@tanstack/ai 0.46.0 → 0.47.2
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 +440 -79
- 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 +3 -3
- 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 +1030 -211
- 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
|
|
@@ -1073,9 +1183,8 @@ class TextEngine<
|
|
|
1073
1183
|
|
|
1074
1184
|
if (!skipAgentLoop) {
|
|
1075
1185
|
do {
|
|
1076
|
-
if (this.earlyTermination
|
|
1077
|
-
|
|
1078
|
-
}
|
|
1186
|
+
if (this.earlyTermination) break
|
|
1187
|
+
if (this.isCancelled()) return
|
|
1079
1188
|
|
|
1080
1189
|
this.logger.agentLoop(`iteration=${this.middlewareCtx.iteration}`, {
|
|
1081
1190
|
iteration: this.middlewareCtx.iteration,
|
|
@@ -1095,7 +1204,37 @@ class TextEngine<
|
|
|
1095
1204
|
)
|
|
1096
1205
|
this.applyMiddlewareConfig(iterTransformedConfig)
|
|
1097
1206
|
|
|
1207
|
+
if (
|
|
1208
|
+
yield* this.emitBoundaryInterrupts(
|
|
1209
|
+
'beforeModel',
|
|
1210
|
+
this.createSyntheticFinishedEvent(),
|
|
1211
|
+
)
|
|
1212
|
+
) {
|
|
1213
|
+
this.setToolPhase('wait')
|
|
1214
|
+
return
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1098
1217
|
yield* this.streamModelResponse()
|
|
1218
|
+
|
|
1219
|
+
if (this.earlyTermination) break
|
|
1220
|
+
|
|
1221
|
+
if (
|
|
1222
|
+
yield* this.emitBoundaryInterrupts(
|
|
1223
|
+
'afterModel',
|
|
1224
|
+
this.finishedEvent ?? this.createSyntheticFinishedEvent(),
|
|
1225
|
+
)
|
|
1226
|
+
) {
|
|
1227
|
+
this.setToolPhase('wait')
|
|
1228
|
+
return
|
|
1229
|
+
}
|
|
1230
|
+
if (this.shouldExecuteToolPhase()) {
|
|
1231
|
+
this.deferredToolCallRunFinishedChunks.push(
|
|
1232
|
+
...this.deferredModelRunFinishedChunks,
|
|
1233
|
+
)
|
|
1234
|
+
this.deferredModelRunFinishedChunks = []
|
|
1235
|
+
} else {
|
|
1236
|
+
yield* this.flushDeferredModelRunFinishedChunks()
|
|
1237
|
+
}
|
|
1099
1238
|
} else {
|
|
1100
1239
|
yield* this.processToolCalls()
|
|
1101
1240
|
}
|
|
@@ -1159,7 +1298,7 @@ class TextEngine<
|
|
|
1159
1298
|
duration: Date.now() - this.streamStartTime,
|
|
1160
1299
|
})
|
|
1161
1300
|
} else {
|
|
1162
|
-
this.
|
|
1301
|
+
this.addTerminalAssistantMessages()
|
|
1163
1302
|
this.terminalHookCalled = true
|
|
1164
1303
|
await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
|
|
1165
1304
|
finishReason: this.lastFinishReason,
|
|
@@ -1403,6 +1542,7 @@ class TextEngine<
|
|
|
1403
1542
|
typeof startValue.messageId === 'string'
|
|
1404
1543
|
) {
|
|
1405
1544
|
this.combinedStructuredMessageId = startValue.messageId
|
|
1545
|
+
this.captureStructuredOutputMessageIdentity(startValue.messageId)
|
|
1406
1546
|
}
|
|
1407
1547
|
}
|
|
1408
1548
|
|
|
@@ -1420,6 +1560,11 @@ class TextEngine<
|
|
|
1420
1560
|
this.structuredOutputResult = { data: object, rawText: parsed.raw }
|
|
1421
1561
|
this.combinedCompleteEmitted = true
|
|
1422
1562
|
const value = chunk.value
|
|
1563
|
+
const completeMessageId = readCustomEventMessageId(value)
|
|
1564
|
+
if (completeMessageId) {
|
|
1565
|
+
this.combinedStructuredMessageId = completeMessageId
|
|
1566
|
+
this.captureStructuredOutputMessageIdentity(completeMessageId)
|
|
1567
|
+
}
|
|
1423
1568
|
if (object !== parsed.object && value && typeof value === 'object') {
|
|
1424
1569
|
outboundChunk = { ...chunk, value: { ...value, object } }
|
|
1425
1570
|
}
|
|
@@ -1482,10 +1627,17 @@ class TextEngine<
|
|
|
1482
1627
|
) {
|
|
1483
1628
|
continue
|
|
1484
1629
|
}
|
|
1630
|
+
if (outputChunk.type === EventType.RUN_FINISHED) {
|
|
1631
|
+
this.deferredModelRunFinishedChunks.push(outputChunk)
|
|
1632
|
+
continue
|
|
1633
|
+
}
|
|
1485
1634
|
if (this.shouldDeferToolCallRunFinished(outputChunk)) {
|
|
1486
1635
|
this.deferredToolCallRunFinishedChunks.push(outputChunk)
|
|
1487
1636
|
continue
|
|
1488
1637
|
}
|
|
1638
|
+
if (outputChunk.type === EventType.RUN_STARTED) {
|
|
1639
|
+
this.hasPublicRunStarted = true
|
|
1640
|
+
}
|
|
1489
1641
|
this.logger.output(`type=${outputChunk.type}`, { chunk: outputChunk })
|
|
1490
1642
|
yield outputChunk
|
|
1491
1643
|
this.middlewareCtx.chunkIndex++
|
|
@@ -1586,6 +1738,11 @@ class TextEngine<
|
|
|
1586
1738
|
}
|
|
1587
1739
|
}
|
|
1588
1740
|
|
|
1741
|
+
private captureStructuredOutputMessageIdentity(messageId: string): void {
|
|
1742
|
+
this.structuredOutputMessageId = messageId
|
|
1743
|
+
this.structuredOutputMessageCreatedAt ??= new Date()
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1589
1746
|
private handleToolCallStartEvent(chunk: ToolCallStartEvent): void {
|
|
1590
1747
|
if (
|
|
1591
1748
|
typeof chunk.parentMessageId === 'string' &&
|
|
@@ -1627,11 +1784,8 @@ class TextEngine<
|
|
|
1627
1784
|
chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,
|
|
1628
1785
|
): void {
|
|
1629
1786
|
this.earlyTermination = true
|
|
1630
|
-
if (this.
|
|
1631
|
-
const message =
|
|
1632
|
-
chunk.message ||
|
|
1633
|
-
chunk.error?.message ||
|
|
1634
|
-
'Run failed before structured output completed'
|
|
1787
|
+
if (this.finalizationError === null) {
|
|
1788
|
+
const message = chunk.message || chunk.error?.message || 'Run failed'
|
|
1635
1789
|
this.finalizationError = {
|
|
1636
1790
|
message,
|
|
1637
1791
|
...(chunk.code !== undefined
|
|
@@ -1763,6 +1917,18 @@ class TextEngine<
|
|
|
1763
1917
|
return 'continue'
|
|
1764
1918
|
}
|
|
1765
1919
|
|
|
1920
|
+
this.middlewareCtx.phase = 'beforeTools'
|
|
1921
|
+
if (
|
|
1922
|
+
yield* this.emitBoundaryInterrupts(
|
|
1923
|
+
'beforeTools',
|
|
1924
|
+
finishEvent,
|
|
1925
|
+
executablePendingCalls,
|
|
1926
|
+
)
|
|
1927
|
+
) {
|
|
1928
|
+
this.setToolPhase('wait')
|
|
1929
|
+
return 'wait'
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1766
1932
|
const { approvals, clientToolResults } = this.collectClientState()
|
|
1767
1933
|
|
|
1768
1934
|
const generator = executeToolCalls(
|
|
@@ -1930,6 +2096,17 @@ class TextEngine<
|
|
|
1930
2096
|
}
|
|
1931
2097
|
this.middlewareCtx.phase = 'beforeTools'
|
|
1932
2098
|
|
|
2099
|
+
if (
|
|
2100
|
+
yield* this.emitBoundaryInterrupts(
|
|
2101
|
+
'beforeTools',
|
|
2102
|
+
finishEvent,
|
|
2103
|
+
executableToolCalls,
|
|
2104
|
+
)
|
|
2105
|
+
) {
|
|
2106
|
+
this.setToolPhase('wait')
|
|
2107
|
+
return
|
|
2108
|
+
}
|
|
2109
|
+
|
|
1933
2110
|
const { approvals, clientToolResults } = this.collectClientState()
|
|
1934
2111
|
|
|
1935
2112
|
const generator = executeToolCalls(
|
|
@@ -1997,15 +2174,36 @@ class TextEngine<
|
|
|
1997
2174
|
needsClientExecution: executionResult.needsClientExecution,
|
|
1998
2175
|
})
|
|
1999
2176
|
|
|
2177
|
+
const afterToolBoundaryChunks = this.buildToolResultChunks(
|
|
2178
|
+
allResults,
|
|
2179
|
+
finishEvent,
|
|
2180
|
+
)
|
|
2181
|
+
const afterToolRequests =
|
|
2182
|
+
await this.middlewareRunner.runOnInterruptBoundary(
|
|
2183
|
+
this.middlewareCtx as ChatMiddlewareContext<TContext> & {
|
|
2184
|
+
phase: 'afterTools'
|
|
2185
|
+
},
|
|
2186
|
+
)
|
|
2187
|
+
if (afterToolRequests.length > 0) {
|
|
2188
|
+
for (const chunk of afterToolBoundaryChunks) {
|
|
2189
|
+
yield* this.pipeThroughMiddleware(chunk)
|
|
2190
|
+
}
|
|
2191
|
+
yield* this.emitBoundaryInterrupts(
|
|
2192
|
+
'afterTools',
|
|
2193
|
+
finishEvent,
|
|
2194
|
+
toolCalls,
|
|
2195
|
+
afterToolRequests,
|
|
2196
|
+
)
|
|
2197
|
+
this.setToolPhase('wait')
|
|
2198
|
+
return
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2000
2201
|
if (
|
|
2001
2202
|
executionResult.needsApproval.length > 0 ||
|
|
2002
2203
|
executionResult.needsClientExecution.length > 0
|
|
2003
2204
|
) {
|
|
2004
2205
|
if (allResults.length > 0) {
|
|
2005
|
-
for (const chunk of
|
|
2006
|
-
allResults,
|
|
2007
|
-
finishEvent,
|
|
2008
|
-
)) {
|
|
2206
|
+
for (const chunk of afterToolBoundaryChunks) {
|
|
2009
2207
|
yield* this.pipeThroughMiddleware(chunk)
|
|
2010
2208
|
}
|
|
2011
2209
|
}
|
|
@@ -2021,7 +2219,7 @@ class TextEngine<
|
|
|
2021
2219
|
|
|
2022
2220
|
yield* this.flushDeferredToolCallRunFinishedChunks()
|
|
2023
2221
|
|
|
2024
|
-
const toolResultChunks =
|
|
2222
|
+
const toolResultChunks = afterToolBoundaryChunks
|
|
2025
2223
|
|
|
2026
2224
|
for (const chunk of toolResultChunks) {
|
|
2027
2225
|
yield* this.pipeThroughMiddleware(chunk)
|
|
@@ -2061,6 +2259,48 @@ class TextEngine<
|
|
|
2061
2259
|
this.deferredToolCallRunFinishedChunks = []
|
|
2062
2260
|
}
|
|
2063
2261
|
|
|
2262
|
+
private *flushDeferredModelRunFinishedChunks(): Generator<StreamChunk> {
|
|
2263
|
+
for (const chunk of this.deferredModelRunFinishedChunks) {
|
|
2264
|
+
this.logger.output(`type=${chunk.type}`, { chunk })
|
|
2265
|
+
yield chunk
|
|
2266
|
+
this.middlewareCtx.chunkIndex++
|
|
2267
|
+
}
|
|
2268
|
+
this.deferredModelRunFinishedChunks = []
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
private async *emitSyntheticRunStarted(
|
|
2272
|
+
finishEvent: RunFinishedEvent,
|
|
2273
|
+
): AsyncGenerator<StreamChunk, void, void> {
|
|
2274
|
+
if (this.hasPublicRunStarted) return
|
|
2275
|
+
yield* this.pipeThroughMiddleware({
|
|
2276
|
+
type: EventType.RUN_STARTED,
|
|
2277
|
+
runId: finishEvent.runId,
|
|
2278
|
+
threadId: finishEvent.threadId,
|
|
2279
|
+
model: finishEvent.model,
|
|
2280
|
+
timestamp: Date.now(),
|
|
2281
|
+
})
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
private async *emitSuccessfulEarlyTermination(): AsyncGenerator<
|
|
2285
|
+
StreamChunk,
|
|
2286
|
+
void,
|
|
2287
|
+
void
|
|
2288
|
+
> {
|
|
2289
|
+
// `stop` is a finished run, not another tool cycle. `tool_calls` here
|
|
2290
|
+
// makes the client auto-send after afterTools, so reject looks stuck.
|
|
2291
|
+
this.lastFinishReason = 'stop'
|
|
2292
|
+
const finishEvent = {
|
|
2293
|
+
...this.createSyntheticFinishedEvent(),
|
|
2294
|
+
finishReason: 'stop' as const,
|
|
2295
|
+
}
|
|
2296
|
+
yield* this.emitSyntheticRunStarted(finishEvent)
|
|
2297
|
+
yield* this.pipeThroughMiddleware({
|
|
2298
|
+
...finishEvent,
|
|
2299
|
+
timestamp: Date.now(),
|
|
2300
|
+
outcome: { type: 'success' },
|
|
2301
|
+
})
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2064
2304
|
private discardDeferredToolCallRunFinishedChunks(): void {
|
|
2065
2305
|
this.deferredToolCallRunFinishedChunks = []
|
|
2066
2306
|
}
|
|
@@ -2092,27 +2332,104 @@ class TextEngine<
|
|
|
2092
2332
|
this.middlewareCtx.messages = this.messages
|
|
2093
2333
|
}
|
|
2094
2334
|
|
|
2095
|
-
private
|
|
2335
|
+
private addTerminalAssistantMessages(): void {
|
|
2096
2336
|
this.finalizeCurrentThinkingStep()
|
|
2097
|
-
if (this.accumulatedThinking.length === 0) return
|
|
2098
2337
|
|
|
2099
|
-
const
|
|
2100
|
-
const
|
|
2338
|
+
const structuredResult = this.structuredOutputResult
|
|
2339
|
+
const raw = structuredResult
|
|
2340
|
+
? structuredResult.rawText || safeJsonStringify(structuredResult.data)
|
|
2341
|
+
: ''
|
|
2342
|
+
const structuredOutput: StructuredOutputPart | undefined = structuredResult
|
|
2343
|
+
? {
|
|
2344
|
+
type: 'structured-output',
|
|
2345
|
+
status: 'complete',
|
|
2346
|
+
data: structuredResult.data,
|
|
2347
|
+
partial: structuredResult.data,
|
|
2348
|
+
raw,
|
|
2349
|
+
...(structuredResult.reasoning !== undefined
|
|
2350
|
+
? { reasoning: structuredResult.reasoning }
|
|
2351
|
+
: {}),
|
|
2352
|
+
}
|
|
2353
|
+
: undefined
|
|
2354
|
+
const nativeCombined = this.finalStructuredOutput?.nativeCombined === true
|
|
2355
|
+
const eventSourced = this.finalStructuredOutput?.source === 'event'
|
|
2356
|
+
const structuredId =
|
|
2357
|
+
this.structuredOutputMessageId ??
|
|
2358
|
+
this.combinedStructuredMessageId ??
|
|
2359
|
+
this.currentMessageId ??
|
|
2360
|
+
this.createId('msg')
|
|
2361
|
+
// Codex, OpenCode, ACP, and grok-build reuse the last text messageId
|
|
2362
|
+
// on structured-output.complete. Split only when the event uses a
|
|
2363
|
+
// different id (Claude Code). Same-id output stays on one message.
|
|
2364
|
+
const splitStructuredMessage =
|
|
2365
|
+
Boolean(structuredOutput) &&
|
|
2366
|
+
(!nativeCombined || eventSourced) &&
|
|
2367
|
+
this.currentMessageId != null &&
|
|
2368
|
+
structuredId !== this.currentMessageId
|
|
2369
|
+
const messages = [...this.middlewareCtx.messages]
|
|
2370
|
+
const existingStructuredIndex = messages.findIndex(
|
|
2371
|
+
(message) => message.role === 'assistant' && message.id === structuredId,
|
|
2372
|
+
)
|
|
2373
|
+
const currentTurnAlreadyRecorded = messages.some(
|
|
2101
2374
|
(message) =>
|
|
2102
2375
|
message.role === 'assistant' && message.id === this.currentMessageId,
|
|
2103
2376
|
)
|
|
2104
|
-
|
|
2377
|
+
const thinking =
|
|
2378
|
+
this.accumulatedThinking.length > 0 ? this.accumulatedThinking : undefined
|
|
2379
|
+
const startedLength = messages.length
|
|
2380
|
+
|
|
2381
|
+
if (structuredOutput && existingStructuredIndex >= 0) {
|
|
2382
|
+
const existing = messages[existingStructuredIndex]
|
|
2383
|
+
if (existing) {
|
|
2384
|
+
messages[existingStructuredIndex] = {
|
|
2385
|
+
...existing,
|
|
2386
|
+
content: raw || existing.content,
|
|
2387
|
+
structuredOutput,
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
} else if (structuredOutput && !splitStructuredMessage) {
|
|
2391
|
+
if (!currentTurnAlreadyRecorded) {
|
|
2392
|
+
messages.push({
|
|
2393
|
+
role: 'assistant',
|
|
2394
|
+
content: this.accumulatedContent || raw || null,
|
|
2395
|
+
id: structuredId,
|
|
2396
|
+
createdAt:
|
|
2397
|
+
this.currentMessageCreatedAt ??
|
|
2398
|
+
this.structuredOutputMessageCreatedAt ??
|
|
2399
|
+
new Date(),
|
|
2400
|
+
structuredOutput,
|
|
2401
|
+
...(thinking ? { thinking } : {}),
|
|
2402
|
+
})
|
|
2403
|
+
}
|
|
2404
|
+
} else {
|
|
2405
|
+
if (
|
|
2406
|
+
!currentTurnAlreadyRecorded &&
|
|
2407
|
+
(this.accumulatedContent !== '' || thinking)
|
|
2408
|
+
) {
|
|
2409
|
+
messages.push({
|
|
2410
|
+
role: 'assistant',
|
|
2411
|
+
content: this.accumulatedContent || null,
|
|
2412
|
+
id: this.currentMessageId ?? this.createId('msg'),
|
|
2413
|
+
createdAt: this.currentMessageCreatedAt ?? new Date(),
|
|
2414
|
+
...(thinking ? { thinking } : {}),
|
|
2415
|
+
})
|
|
2416
|
+
}
|
|
2417
|
+
if (structuredOutput) {
|
|
2418
|
+
messages.push({
|
|
2419
|
+
role: 'assistant',
|
|
2420
|
+
content: raw || null,
|
|
2421
|
+
id: structuredId,
|
|
2422
|
+
createdAt: this.structuredOutputMessageCreatedAt ?? new Date(),
|
|
2423
|
+
structuredOutput,
|
|
2424
|
+
})
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2105
2427
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
id: this.currentMessageId ?? undefined,
|
|
2112
|
-
createdAt: this.currentMessageCreatedAt ?? undefined,
|
|
2113
|
-
thinking: this.accumulatedThinking,
|
|
2114
|
-
},
|
|
2115
|
-
]
|
|
2428
|
+
if (messages.length === startedLength && existingStructuredIndex < 0) {
|
|
2429
|
+
return
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
this.messages = messages
|
|
2116
2433
|
this.middlewareCtx.messages = this.messages
|
|
2117
2434
|
}
|
|
2118
2435
|
|
|
@@ -2206,9 +2523,17 @@ class TextEngine<
|
|
|
2206
2523
|
return { approvals, clientToolResults }
|
|
2207
2524
|
}
|
|
2208
2525
|
|
|
2526
|
+
private genericInterruptId(): string {
|
|
2527
|
+
return this.createId('interrupt')
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2209
2530
|
private buildActionableInterrupts(
|
|
2210
2531
|
approvals: Array<ApprovalRequest>,
|
|
2211
2532
|
clientRequests: Array<ClientToolRequest>,
|
|
2533
|
+
genericRequests: ReadonlyArray<
|
|
2534
|
+
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2535
|
+
> = [],
|
|
2536
|
+
genericInterruptIds: ReadonlyArray<string> = [],
|
|
2212
2537
|
): Array<Interrupt> {
|
|
2213
2538
|
const interrupts: Array<Interrupt> = []
|
|
2214
2539
|
|
|
@@ -2278,6 +2603,64 @@ class TextEngine<
|
|
|
2278
2603
|
})
|
|
2279
2604
|
}
|
|
2280
2605
|
|
|
2606
|
+
for (const [index, request] of genericRequests.entries()) {
|
|
2607
|
+
const batchIndex = interrupts.length
|
|
2608
|
+
const id = genericInterruptIds[index]
|
|
2609
|
+
if (!id) throw new Error('Generic interrupt id is unavailable.')
|
|
2610
|
+
const preEmission = createInterruptBinding(request, { batchIndex })
|
|
2611
|
+
interrupts.push({
|
|
2612
|
+
id,
|
|
2613
|
+
reason: request.reason,
|
|
2614
|
+
message: request.message,
|
|
2615
|
+
...(preEmission.descriptor.responseSchemaCanonicalJson !== undefined
|
|
2616
|
+
? {
|
|
2617
|
+
responseSchema: JSON.parse(
|
|
2618
|
+
preEmission.descriptor.responseSchemaCanonicalJson,
|
|
2619
|
+
),
|
|
2620
|
+
}
|
|
2621
|
+
: {}),
|
|
2622
|
+
...(request.expiresAt !== undefined
|
|
2623
|
+
? { expiresAt: request.expiresAt }
|
|
2624
|
+
: {}),
|
|
2625
|
+
metadata: {
|
|
2626
|
+
[interruptBindingMetadataKey]: {
|
|
2627
|
+
v: INTERRUPT_BINDING_VERSION,
|
|
2628
|
+
kind: 'generic',
|
|
2629
|
+
interruptId: id,
|
|
2630
|
+
definitionId: preEmission.descriptor.definitionId,
|
|
2631
|
+
key: preEmission.descriptor.key,
|
|
2632
|
+
batchIndex,
|
|
2633
|
+
...(request.expiresAt !== undefined
|
|
2634
|
+
? { expiresAt: request.expiresAt }
|
|
2635
|
+
: {}),
|
|
2636
|
+
...(preEmission.descriptor.payloadSchemaHash
|
|
2637
|
+
? {
|
|
2638
|
+
payloadSchemaHash: preEmission.descriptor.payloadSchemaHash,
|
|
2639
|
+
}
|
|
2640
|
+
: {}),
|
|
2641
|
+
...(preEmission.descriptor.responseSchemaHash !== undefined
|
|
2642
|
+
? {
|
|
2643
|
+
responseSchemaHash: preEmission.descriptor.responseSchemaHash,
|
|
2644
|
+
}
|
|
2645
|
+
: {}),
|
|
2646
|
+
},
|
|
2647
|
+
...(preEmission.payload !== undefined
|
|
2648
|
+
? { [INTERRUPT_PAYLOAD_METADATA_KEY]: preEmission.payload }
|
|
2649
|
+
: {}),
|
|
2650
|
+
},
|
|
2651
|
+
})
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
const ids = new Set<string>()
|
|
2655
|
+
for (const interrupt of interrupts) {
|
|
2656
|
+
if (ids.has(interrupt.id)) {
|
|
2657
|
+
throw new Error(
|
|
2658
|
+
`Duplicate interrupt id in final batch: ${interrupt.id}`,
|
|
2659
|
+
)
|
|
2660
|
+
}
|
|
2661
|
+
ids.add(interrupt.id)
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2281
2664
|
return interrupts
|
|
2282
2665
|
}
|
|
2283
2666
|
|
|
@@ -2285,13 +2668,22 @@ class TextEngine<
|
|
|
2285
2668
|
finishEvent: RunFinishedEvent,
|
|
2286
2669
|
approvals: Array<ApprovalRequest>,
|
|
2287
2670
|
clientRequests: Array<ClientToolRequest>,
|
|
2671
|
+
genericRequests: ReadonlyArray<
|
|
2672
|
+
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2673
|
+
> = [],
|
|
2674
|
+
genericInterruptIds?: ReadonlyArray<string>,
|
|
2288
2675
|
): StreamChunk {
|
|
2289
2676
|
return {
|
|
2290
2677
|
...finishEvent,
|
|
2291
2678
|
timestamp: Date.now(),
|
|
2292
2679
|
outcome: {
|
|
2293
2680
|
type: 'interrupt',
|
|
2294
|
-
interrupts: this.buildActionableInterrupts(
|
|
2681
|
+
interrupts: this.buildActionableInterrupts(
|
|
2682
|
+
approvals,
|
|
2683
|
+
clientRequests,
|
|
2684
|
+
genericRequests,
|
|
2685
|
+
genericInterruptIds,
|
|
2686
|
+
),
|
|
2295
2687
|
},
|
|
2296
2688
|
}
|
|
2297
2689
|
}
|
|
@@ -2309,7 +2701,8 @@ class TextEngine<
|
|
|
2309
2701
|
message.id ||
|
|
2310
2702
|
`snapshot_${this.runIdOverride ?? this.requestId}_${index}`
|
|
2311
2703
|
const parts =
|
|
2312
|
-
message.role === 'assistant' &&
|
|
2704
|
+
message.role === 'assistant' &&
|
|
2705
|
+
(message.thinking?.length || message.structuredOutput)
|
|
2313
2706
|
? modelMessageToUIMessage(message, id).parts
|
|
2314
2707
|
: undefined
|
|
2315
2708
|
return {
|
|
@@ -2440,9 +2833,22 @@ class TextEngine<
|
|
|
2440
2833
|
finishEvent: RunFinishedEvent,
|
|
2441
2834
|
approvals: Array<ApprovalRequest>,
|
|
2442
2835
|
clientRequests: Array<ClientToolRequest>,
|
|
2836
|
+
genericRequests: ReadonlyArray<
|
|
2837
|
+
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2838
|
+
> = [],
|
|
2443
2839
|
): AsyncGenerator<StreamChunk, boolean, void> {
|
|
2840
|
+
yield* this.emitSyntheticRunStarted(finishEvent)
|
|
2841
|
+
const genericInterruptIds = genericRequests.map(() =>
|
|
2842
|
+
this.genericInterruptId(),
|
|
2843
|
+
)
|
|
2444
2844
|
const terminal = this.completeEphemeralInterruptBindings(
|
|
2445
|
-
this.buildInterruptFinishedChunk(
|
|
2845
|
+
this.buildInterruptFinishedChunk(
|
|
2846
|
+
finishEvent,
|
|
2847
|
+
approvals,
|
|
2848
|
+
clientRequests,
|
|
2849
|
+
genericRequests,
|
|
2850
|
+
genericInterruptIds,
|
|
2851
|
+
),
|
|
2446
2852
|
)
|
|
2447
2853
|
let terminalOutputs: Array<StreamChunk>
|
|
2448
2854
|
try {
|
|
@@ -2471,6 +2877,103 @@ class TextEngine<
|
|
|
2471
2877
|
return true
|
|
2472
2878
|
}
|
|
2473
2879
|
|
|
2880
|
+
private async *emitBoundaryInterrupts(
|
|
2881
|
+
phase: 'beforeModel' | 'afterModel' | 'beforeTools' | 'afterTools',
|
|
2882
|
+
finishEvent: RunFinishedEvent,
|
|
2883
|
+
toolCalls: ReadonlyArray<ToolCall> = [],
|
|
2884
|
+
requests?: ReadonlyArray<
|
|
2885
|
+
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2886
|
+
>,
|
|
2887
|
+
): AsyncGenerator<StreamChunk, boolean, void> {
|
|
2888
|
+
this.middlewareCtx.phase = phase
|
|
2889
|
+
const boundaryRequests =
|
|
2890
|
+
requests ??
|
|
2891
|
+
(await this.middlewareRunner.runOnInterruptBoundary(
|
|
2892
|
+
this.middlewareCtx as ChatMiddlewareContext<TContext> & {
|
|
2893
|
+
phase: typeof phase
|
|
2894
|
+
},
|
|
2895
|
+
))
|
|
2896
|
+
if (boundaryRequests.length === 0) return false
|
|
2897
|
+
for (const request of boundaryRequests) {
|
|
2898
|
+
if (
|
|
2899
|
+
this.interruptDefinitions.get(request.definition.id) !==
|
|
2900
|
+
request.definition
|
|
2901
|
+
) {
|
|
2902
|
+
throw new Error(
|
|
2903
|
+
`Generic interrupt definition ${request.definition.id} is not registered on this chat.`,
|
|
2904
|
+
)
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
if (phase === 'afterModel') {
|
|
2908
|
+
if (this.toolCallManager.hasToolCalls()) {
|
|
2909
|
+
this.addAssistantToolCallMessage(this.toolCallManager.getToolCalls())
|
|
2910
|
+
} else {
|
|
2911
|
+
this.addAssistantTextMessageForInterrupt()
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
const actionable = this.getBoundaryActionableToolRequests(toolCalls)
|
|
2915
|
+
yield* this.emitActionableInterruptBoundary(
|
|
2916
|
+
finishEvent,
|
|
2917
|
+
actionable.approvals,
|
|
2918
|
+
actionable.clientRequests,
|
|
2919
|
+
boundaryRequests,
|
|
2920
|
+
)
|
|
2921
|
+
return true
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
private addAssistantTextMessageForInterrupt(): void {
|
|
2925
|
+
if (this.accumulatedContent.length === 0) return
|
|
2926
|
+
this.messages = [
|
|
2927
|
+
...this.messages,
|
|
2928
|
+
{ role: 'assistant', content: this.accumulatedContent },
|
|
2929
|
+
]
|
|
2930
|
+
this.middlewareCtx.messages = this.messages
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
private getBoundaryActionableToolRequests(
|
|
2934
|
+
toolCalls: ReadonlyArray<ToolCall>,
|
|
2935
|
+
): {
|
|
2936
|
+
approvals: Array<ApprovalRequest>
|
|
2937
|
+
clientRequests: Array<ClientToolRequest>
|
|
2938
|
+
} {
|
|
2939
|
+
const { approvals, clientToolResults } = this.collectClientState()
|
|
2940
|
+
const approvalRequests: Array<ApprovalRequest> = []
|
|
2941
|
+
const clientRequests: Array<ClientToolRequest> = []
|
|
2942
|
+
for (const toolCall of toolCalls) {
|
|
2943
|
+
const tool = this.resolveExecutableTools([toolCall]).find(
|
|
2944
|
+
(candidate) => candidate.name === toolCall.function.name,
|
|
2945
|
+
) as RuntimeToolWithApproval | undefined
|
|
2946
|
+
if (!tool) continue
|
|
2947
|
+
let input: unknown = {}
|
|
2948
|
+
try {
|
|
2949
|
+
const parsed = JSON.parse(toolCall.function.arguments.trim() || '{}')
|
|
2950
|
+
input = parsed && typeof parsed === 'object' ? parsed : {}
|
|
2951
|
+
} catch {
|
|
2952
|
+
input = {}
|
|
2953
|
+
}
|
|
2954
|
+
const approvalId = `approval_${toolCall.id}`
|
|
2955
|
+
if (tool.needsApproval && !approvals.has(approvalId)) {
|
|
2956
|
+
approvalRequests.push({
|
|
2957
|
+
toolCallId: toolCall.id,
|
|
2958
|
+
toolName: toolCall.function.name,
|
|
2959
|
+
input,
|
|
2960
|
+
approvalId,
|
|
2961
|
+
})
|
|
2962
|
+
} else if (
|
|
2963
|
+
!tool.execute &&
|
|
2964
|
+
!clientToolResults.has(toolCall.id) &&
|
|
2965
|
+
!this.resumeCancelledToolCallIds.has(toolCall.id)
|
|
2966
|
+
) {
|
|
2967
|
+
clientRequests.push({
|
|
2968
|
+
toolCallId: toolCall.id,
|
|
2969
|
+
toolName: toolCall.function.name,
|
|
2970
|
+
input,
|
|
2971
|
+
})
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
return { approvals: approvalRequests, clientRequests }
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2474
2977
|
private completeEphemeralInterruptBindings(chunk: StreamChunk): StreamChunk {
|
|
2475
2978
|
if (
|
|
2476
2979
|
chunk.type !== EventType.RUN_FINISHED ||
|
|
@@ -2718,7 +3221,7 @@ class TextEngine<
|
|
|
2718
3221
|
private createSyntheticFinishedEvent(): RunFinishedEvent {
|
|
2719
3222
|
return {
|
|
2720
3223
|
type: 'RUN_FINISHED',
|
|
2721
|
-
runId: this.
|
|
3224
|
+
runId: this.runIdOverride ?? this.requestId,
|
|
2722
3225
|
threadId: this.threadId,
|
|
2723
3226
|
model: this.params.model,
|
|
2724
3227
|
timestamp: Date.now(),
|
|
@@ -2997,6 +3500,7 @@ class TextEngine<
|
|
|
2997
3500
|
const buildSynthesizedStart = (timestamp = Date.now()): StreamChunk => {
|
|
2998
3501
|
const idForStart = structuredMessageId ?? generateMessageId()
|
|
2999
3502
|
structuredMessageId = idForStart
|
|
3503
|
+
this.captureStructuredOutputMessageIdentity(idForStart)
|
|
3000
3504
|
return {
|
|
3001
3505
|
type: EventType.CUSTOM,
|
|
3002
3506
|
name: 'structured-output.start',
|
|
@@ -3037,7 +3541,10 @@ class TextEngine<
|
|
|
3037
3541
|
// synthesized start (when needed) uses the SAME id the deltas carry
|
|
3038
3542
|
if (!structuredMessageId) {
|
|
3039
3543
|
const extracted = extractMessageId(chunk)
|
|
3040
|
-
if (extracted)
|
|
3544
|
+
if (extracted) {
|
|
3545
|
+
structuredMessageId = extracted
|
|
3546
|
+
this.captureStructuredOutputMessageIdentity(extracted)
|
|
3547
|
+
}
|
|
3041
3548
|
}
|
|
3042
3549
|
|
|
3043
3550
|
// Synthesis only matters for the streaming client path — the agentic
|
|
@@ -3104,7 +3611,13 @@ class TextEngine<
|
|
|
3104
3611
|
const object = this.finalStructuredOutput.normalize
|
|
3105
3612
|
? this.finalStructuredOutput.normalize(parsed.object)
|
|
3106
3613
|
: parsed.object
|
|
3107
|
-
this.structuredOutputResult = {
|
|
3614
|
+
this.structuredOutputResult = {
|
|
3615
|
+
data: object,
|
|
3616
|
+
rawText: parsed.raw,
|
|
3617
|
+
...(parsed.reasoning !== undefined
|
|
3618
|
+
? { reasoning: parsed.reasoning }
|
|
3619
|
+
: {}),
|
|
3620
|
+
}
|
|
3108
3621
|
// Rewrite the outbound event so the yielded chunk carries the
|
|
3109
3622
|
// normalized object (the original `chunk.value` still holds the
|
|
3110
3623
|
// widened one). Preserve every other field — `raw`, `reasoning` —
|
|
@@ -3555,7 +4068,15 @@ class TextEngine<
|
|
|
3555
4068
|
}
|
|
3556
4069
|
}
|
|
3557
4070
|
|
|
3558
|
-
const
|
|
4071
|
+
const genericPending = this.getGenericContinuationPending(interruptedRunId)
|
|
4072
|
+
const pending: Array<{
|
|
4073
|
+
interruptId: string
|
|
4074
|
+
payload: unknown
|
|
4075
|
+
binding: InterruptBinding
|
|
4076
|
+
genericRequest?: GenericInterruptRequest<
|
|
4077
|
+
InterruptDefinition<any, any, any, any>
|
|
4078
|
+
>
|
|
4079
|
+
}> = this.buildActionableInterrupts(
|
|
3559
4080
|
approvalRequests,
|
|
3560
4081
|
clientRequests,
|
|
3561
4082
|
).flatMap((descriptor) => {
|
|
@@ -3574,6 +4095,7 @@ class TextEngine<
|
|
|
3574
4095
|
]
|
|
3575
4096
|
: []
|
|
3576
4097
|
})
|
|
4098
|
+
pending.push(...genericPending)
|
|
3577
4099
|
const validated = await validateInterruptResumeBatch({
|
|
3578
4100
|
threadId: this.threadId,
|
|
3579
4101
|
interruptedRunId,
|
|
@@ -3601,6 +4123,185 @@ class TextEngine<
|
|
|
3601
4123
|
...validated.resumeToolState,
|
|
3602
4124
|
approvals,
|
|
3603
4125
|
})
|
|
4126
|
+
|
|
4127
|
+
const genericResolutions = validated.resumeToolState.genericInterrupts
|
|
4128
|
+
if (genericPending.length > 0 && genericResolutions) {
|
|
4129
|
+
const resolutions = genericPending
|
|
4130
|
+
.sort((left, right) => {
|
|
4131
|
+
const leftIndex =
|
|
4132
|
+
left.binding.kind === 'generic' ? (left.binding.batchIndex ?? 0) : 0
|
|
4133
|
+
const rightIndex =
|
|
4134
|
+
right.binding.kind === 'generic'
|
|
4135
|
+
? (right.binding.batchIndex ?? 0)
|
|
4136
|
+
: 0
|
|
4137
|
+
return leftIndex - rightIndex
|
|
4138
|
+
})
|
|
4139
|
+
.flatMap((record) => {
|
|
4140
|
+
const resolution = genericResolutions.get(record.interruptId)
|
|
4141
|
+
if (!resolution || !record.genericRequest) return []
|
|
4142
|
+
return [
|
|
4143
|
+
resolution.status === 'resolved'
|
|
4144
|
+
? {
|
|
4145
|
+
request: record.genericRequest,
|
|
4146
|
+
status: 'resolved' as const,
|
|
4147
|
+
response: resolution.payload,
|
|
4148
|
+
}
|
|
4149
|
+
: {
|
|
4150
|
+
request: record.genericRequest,
|
|
4151
|
+
status: 'cancelled' as const,
|
|
4152
|
+
},
|
|
4153
|
+
]
|
|
4154
|
+
})
|
|
4155
|
+
const collection: InterruptResolutionCollection = {
|
|
4156
|
+
for: (definition) =>
|
|
4157
|
+
resolutions.filter(
|
|
4158
|
+
(resolution) => resolution.request.definition === definition,
|
|
4159
|
+
) as never,
|
|
4160
|
+
all: (
|
|
4161
|
+
...definitions: Array<InterruptDefinition<any, any, any, any>>
|
|
4162
|
+
) =>
|
|
4163
|
+
definitions.length === 0
|
|
4164
|
+
? resolutions
|
|
4165
|
+
: resolutions.filter((resolution) =>
|
|
4166
|
+
definitions.includes(resolution.request.definition),
|
|
4167
|
+
),
|
|
4168
|
+
}
|
|
4169
|
+
const policy = await this.middlewareRunner.runOnInterruptResolution(
|
|
4170
|
+
this.middlewareCtx,
|
|
4171
|
+
collection,
|
|
4172
|
+
)
|
|
4173
|
+
if (policy.toolResume === 'stop') {
|
|
4174
|
+
this.earlyTermination = true
|
|
4175
|
+
} else if (policy.toolResume === 'cancel') {
|
|
4176
|
+
for (const request of pendingToolCalls) {
|
|
4177
|
+
this.resumeCancelledToolCallIds.add(request.id)
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
private getGenericContinuationPending(interruptedRunId: string): Array<{
|
|
4184
|
+
interruptId: string
|
|
4185
|
+
payload: unknown
|
|
4186
|
+
binding: InterruptBinding
|
|
4187
|
+
genericRequest: GenericInterruptRequest<
|
|
4188
|
+
InterruptDefinition<any, any, any, any>
|
|
4189
|
+
>
|
|
4190
|
+
}> {
|
|
4191
|
+
const fail = (message: string): never => {
|
|
4192
|
+
throw new InterruptResumeValidationError([
|
|
4193
|
+
{
|
|
4194
|
+
scope: 'batch',
|
|
4195
|
+
threadId: this.threadId,
|
|
4196
|
+
interruptedRunId,
|
|
4197
|
+
generation: 0,
|
|
4198
|
+
interruptIds: [],
|
|
4199
|
+
code: 'stale',
|
|
4200
|
+
message,
|
|
4201
|
+
source: 'server',
|
|
4202
|
+
retryable: false,
|
|
4203
|
+
},
|
|
4204
|
+
])
|
|
4205
|
+
}
|
|
4206
|
+
const pending: Array<{
|
|
4207
|
+
interruptId: string
|
|
4208
|
+
payload: unknown
|
|
4209
|
+
binding: InterruptBinding
|
|
4210
|
+
genericRequest: GenericInterruptRequest<
|
|
4211
|
+
InterruptDefinition<any, any, any, any>
|
|
4212
|
+
>
|
|
4213
|
+
}> = []
|
|
4214
|
+
const ids = new Set<string>()
|
|
4215
|
+
const batchIndexes = new Set<number>()
|
|
4216
|
+
for (const resumeItem of this.params.resume ?? []) {
|
|
4217
|
+
const parsed = readGenericInterruptContinuation(resumeItem.metadata)
|
|
4218
|
+
if (parsed.status === 'absent') continue
|
|
4219
|
+
if (parsed.status === 'invalid') {
|
|
4220
|
+
return fail(parsed.message)
|
|
4221
|
+
}
|
|
4222
|
+
const entry = parsed.value
|
|
4223
|
+
const id = resumeItem.interruptId
|
|
4224
|
+
const definition = this.interruptDefinitions.get(entry.definitionId)
|
|
4225
|
+
if (!definition) {
|
|
4226
|
+
return fail(
|
|
4227
|
+
`Generic interrupt definition ${entry.definitionId} is unavailable.`,
|
|
4228
|
+
)
|
|
4229
|
+
}
|
|
4230
|
+
if (ids.has(id) || batchIndexes.has(entry.batchIndex)) {
|
|
4231
|
+
return fail(
|
|
4232
|
+
'Generic interrupt continuation contains duplicate entries.',
|
|
4233
|
+
)
|
|
4234
|
+
}
|
|
4235
|
+
ids.add(id)
|
|
4236
|
+
batchIndexes.add(entry.batchIndex)
|
|
4237
|
+
let request: GenericInterruptRequest<
|
|
4238
|
+
InterruptDefinition<any, any, any, any>
|
|
4239
|
+
>
|
|
4240
|
+
try {
|
|
4241
|
+
request = rehydrateInterruptRequest(definition, {
|
|
4242
|
+
key: entry.key,
|
|
4243
|
+
reason: entry.reason,
|
|
4244
|
+
message: entry.message,
|
|
4245
|
+
...(typeof entry.expiresAt === 'string'
|
|
4246
|
+
? { expiresAt: entry.expiresAt }
|
|
4247
|
+
: {}),
|
|
4248
|
+
...(Object.prototype.hasOwnProperty.call(entry, 'payload')
|
|
4249
|
+
? { payload: entry.payload }
|
|
4250
|
+
: {}),
|
|
4251
|
+
})
|
|
4252
|
+
} catch (error) {
|
|
4253
|
+
return fail(
|
|
4254
|
+
`Generic interrupt continuation ${id} is invalid: ${
|
|
4255
|
+
error instanceof Error ? error.message : String(error)
|
|
4256
|
+
}`,
|
|
4257
|
+
)
|
|
4258
|
+
}
|
|
4259
|
+
const emitted = createInterruptBinding(request, {
|
|
4260
|
+
batchIndex: entry.batchIndex,
|
|
4261
|
+
})
|
|
4262
|
+
if (
|
|
4263
|
+
entry.responseSchemaHash !== emitted.descriptor.responseSchemaHash ||
|
|
4264
|
+
entry.payloadSchemaHash !== emitted.descriptor.payloadSchemaHash
|
|
4265
|
+
) {
|
|
4266
|
+
return fail(
|
|
4267
|
+
`Generic interrupt continuation ${id} does not match its definition.`,
|
|
4268
|
+
)
|
|
4269
|
+
}
|
|
4270
|
+
pending.push({
|
|
4271
|
+
interruptId: id,
|
|
4272
|
+
payload: {
|
|
4273
|
+
id,
|
|
4274
|
+
...(emitted.descriptor.responseSchemaCanonicalJson !== undefined
|
|
4275
|
+
? {
|
|
4276
|
+
responseSchema: JSON.parse(
|
|
4277
|
+
emitted.descriptor.responseSchemaCanonicalJson,
|
|
4278
|
+
),
|
|
4279
|
+
}
|
|
4280
|
+
: {}),
|
|
4281
|
+
},
|
|
4282
|
+
binding: {
|
|
4283
|
+
v: INTERRUPT_BINDING_VERSION,
|
|
4284
|
+
kind: 'generic',
|
|
4285
|
+
interruptId: id,
|
|
4286
|
+
interruptedRunId,
|
|
4287
|
+
generation: 0,
|
|
4288
|
+
definitionId: entry.definitionId,
|
|
4289
|
+
key: entry.key,
|
|
4290
|
+
batchIndex: entry.batchIndex,
|
|
4291
|
+
...(typeof entry.expiresAt === 'string'
|
|
4292
|
+
? { expiresAt: entry.expiresAt }
|
|
4293
|
+
: {}),
|
|
4294
|
+
...(emitted.descriptor.payloadSchemaHash
|
|
4295
|
+
? { payloadSchemaHash: emitted.descriptor.payloadSchemaHash }
|
|
4296
|
+
: {}),
|
|
4297
|
+
...(entry.responseSchemaHash !== undefined
|
|
4298
|
+
? { responseSchemaHash: entry.responseSchemaHash }
|
|
4299
|
+
: {}),
|
|
4300
|
+
},
|
|
4301
|
+
genericRequest: request,
|
|
4302
|
+
})
|
|
4303
|
+
}
|
|
4304
|
+
return pending
|
|
3604
4305
|
}
|
|
3605
4306
|
|
|
3606
4307
|
private applyResumeToolState(state: ChatResumeToolState | undefined): void {
|
|
@@ -3624,6 +4325,58 @@ class TextEngine<
|
|
|
3624
4325
|
this.resumeCancelledToolCallIds.add(toolCallId)
|
|
3625
4326
|
}
|
|
3626
4327
|
}
|
|
4328
|
+
if (state?.genericInterrupts) {
|
|
4329
|
+
for (const [interruptId, resolution] of state.genericInterrupts) {
|
|
4330
|
+
this.resumeGenericInterrupts.set(interruptId, resolution)
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
if (state?.genericInterruptRequests) {
|
|
4334
|
+
for (const [interruptId, request] of state.genericInterruptRequests) {
|
|
4335
|
+
this.resumeGenericInterruptRequests.set(interruptId, request)
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
private async applyDurableGenericInterruptResolution(): Promise<void> {
|
|
4341
|
+
if (this.resumeGenericInterruptRequests.size === 0) return
|
|
4342
|
+
const resolutions = [
|
|
4343
|
+
...this.resumeGenericInterruptRequests.entries(),
|
|
4344
|
+
].flatMap(([interruptId, request]) => {
|
|
4345
|
+
const resolution = this.resumeGenericInterrupts.get(interruptId)
|
|
4346
|
+
if (!resolution) return []
|
|
4347
|
+
return [
|
|
4348
|
+
resolution.status === 'resolved'
|
|
4349
|
+
? {
|
|
4350
|
+
request,
|
|
4351
|
+
status: 'resolved' as const,
|
|
4352
|
+
response: resolution.payload,
|
|
4353
|
+
}
|
|
4354
|
+
: { request, status: 'cancelled' as const },
|
|
4355
|
+
]
|
|
4356
|
+
})
|
|
4357
|
+
const collection: InterruptResolutionCollection = {
|
|
4358
|
+
for: (definition) =>
|
|
4359
|
+
resolutions.filter(
|
|
4360
|
+
(resolution) => resolution.request.definition === definition,
|
|
4361
|
+
) as never,
|
|
4362
|
+
all: (...definitions: Array<InterruptDefinition<any, any, any, any>>) =>
|
|
4363
|
+
definitions.length === 0
|
|
4364
|
+
? resolutions
|
|
4365
|
+
: resolutions.filter((resolution) =>
|
|
4366
|
+
definitions.includes(resolution.request.definition),
|
|
4367
|
+
),
|
|
4368
|
+
}
|
|
4369
|
+
const policy = await this.middlewareRunner.runOnInterruptResolution(
|
|
4370
|
+
this.middlewareCtx,
|
|
4371
|
+
collection,
|
|
4372
|
+
)
|
|
4373
|
+
if (policy.toolResume === 'stop') {
|
|
4374
|
+
this.earlyTermination = true
|
|
4375
|
+
} else if (policy.toolResume === 'cancel') {
|
|
4376
|
+
for (const toolCall of this.getPendingToolCallsFromMessages()) {
|
|
4377
|
+
this.resumeCancelledToolCallIds.add(toolCall.id)
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
3627
4380
|
}
|
|
3628
4381
|
|
|
3629
4382
|
private applyMiddlewareConfig(config: ChatMiddlewareConfig): void {
|
|
@@ -3662,6 +4415,9 @@ class TextEngine<
|
|
|
3662
4415
|
chunk,
|
|
3663
4416
|
)
|
|
3664
4417
|
for (const outputChunk of outputChunks) {
|
|
4418
|
+
if (outputChunk.type === EventType.RUN_STARTED) {
|
|
4419
|
+
this.hasPublicRunStarted = true
|
|
4420
|
+
}
|
|
3665
4421
|
yield outputChunk
|
|
3666
4422
|
this.middlewareCtx.chunkIndex++
|
|
3667
4423
|
}
|
|
@@ -3802,67 +4558,136 @@ export function chat<
|
|
|
3802
4558
|
TStream,
|
|
3803
4559
|
any
|
|
3804
4560
|
>['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'],
|
|
4561
|
+
const TInterrupts extends ReadonlyArray<
|
|
4562
|
+
InterruptDefinition<any, any, any, any>
|
|
4563
|
+
> = [],
|
|
4564
|
+
TContext = unknown,
|
|
4565
|
+
const TMiddleware extends Array<unknown> | undefined = undefined,
|
|
3816
4566
|
>(
|
|
3817
4567
|
options: TextActivityOptionsWithContext<
|
|
3818
4568
|
TAdapter,
|
|
3819
4569
|
TSchema,
|
|
3820
4570
|
TStream,
|
|
3821
4571
|
TTools,
|
|
4572
|
+
TInterrupts,
|
|
4573
|
+
TContext,
|
|
3822
4574
|
TMiddleware
|
|
3823
4575
|
>,
|
|
3824
4576
|
): TextActivityResult<TSchema, TStream, TTools> {
|
|
3825
|
-
|
|
4577
|
+
validateInterruptDefinitions(options.interrupts)
|
|
4578
|
+
validateCapabilities(
|
|
4579
|
+
readRuntimeMiddleware(options.middleware) ?? [],
|
|
4580
|
+
options.adapter,
|
|
4581
|
+
)
|
|
3826
4582
|
if (options.tools) {
|
|
3827
4583
|
assertUniqueToolNames(options.tools)
|
|
3828
4584
|
}
|
|
3829
4585
|
|
|
3830
4586
|
const { outputSchema, stream } = options
|
|
3831
4587
|
|
|
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
4588
|
if (outputSchema && stream === true) {
|
|
3836
|
-
return runStreamingStructuredOutput(
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
4589
|
+
return runStreamingStructuredOutput(
|
|
4590
|
+
toRuntimeTextActivityOptions(options, {
|
|
4591
|
+
outputSchema,
|
|
4592
|
+
stream: true,
|
|
4593
|
+
}),
|
|
4594
|
+
) as TextActivityResult<TSchema, TStream, TTools>
|
|
3841
4595
|
}
|
|
3842
4596
|
|
|
3843
|
-
// If outputSchema is provided, run agentic structured output (Promise<T>)
|
|
3844
4597
|
if (outputSchema) {
|
|
3845
|
-
return runAgenticStructuredOutput(
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
4598
|
+
return runAgenticStructuredOutput(
|
|
4599
|
+
toRuntimeTextActivityOptions(options, {
|
|
4600
|
+
outputSchema,
|
|
4601
|
+
stream: false,
|
|
4602
|
+
}),
|
|
4603
|
+
) as TextActivityResult<TSchema, TStream, TTools>
|
|
3849
4604
|
}
|
|
3850
4605
|
|
|
3851
|
-
// If stream is explicitly false, run non-streaming text
|
|
3852
4606
|
if (stream === false) {
|
|
3853
|
-
return runNonStreamingText(
|
|
3854
|
-
|
|
4607
|
+
return runNonStreamingText(
|
|
4608
|
+
toRuntimeTextActivityOptions(options, {
|
|
4609
|
+
outputSchema: undefined,
|
|
4610
|
+
stream: false,
|
|
4611
|
+
}),
|
|
4612
|
+
) as TextActivityResult<TSchema, TStream, TTools>
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4615
|
+
return runStreamingText(
|
|
4616
|
+
toRuntimeTextActivityOptions(options, {
|
|
3855
4617
|
outputSchema: undefined,
|
|
3856
|
-
stream,
|
|
3857
|
-
})
|
|
4618
|
+
stream: true,
|
|
4619
|
+
}),
|
|
4620
|
+
) as TextActivityResult<TSchema, TStream, TTools>
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4623
|
+
type RuntimeTextActivityOptions<
|
|
4624
|
+
TAdapter extends AnyTextAdapter,
|
|
4625
|
+
TSchema extends SchemaInput | undefined,
|
|
4626
|
+
TStream extends boolean,
|
|
4627
|
+
> = Omit<TextActivityOptions<TAdapter, TSchema, TStream, any>, 'middleware'> & {
|
|
4628
|
+
middleware?: Array<AnyChatMiddleware>
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4631
|
+
function readRuntimeMiddleware(
|
|
4632
|
+
middleware: unknown,
|
|
4633
|
+
): Array<AnyChatMiddleware> | undefined {
|
|
4634
|
+
if (middleware === undefined) return undefined
|
|
4635
|
+
if (!Array.isArray(middleware)) {
|
|
4636
|
+
throw new TypeError('Chat middleware must be an array.')
|
|
3858
4637
|
}
|
|
4638
|
+
return middleware
|
|
4639
|
+
}
|
|
3859
4640
|
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
4641
|
+
function toRuntimeTextActivityOptions<
|
|
4642
|
+
TAdapter extends AnyTextAdapter,
|
|
4643
|
+
TInputSchema extends SchemaInput | undefined,
|
|
4644
|
+
TInputStream extends boolean,
|
|
4645
|
+
TOutputSchema extends SchemaInput | undefined,
|
|
4646
|
+
TOutputStream extends boolean,
|
|
4647
|
+
TTools extends TextActivityOptions<
|
|
4648
|
+
TAdapter,
|
|
4649
|
+
TInputSchema,
|
|
4650
|
+
TInputStream,
|
|
4651
|
+
any
|
|
4652
|
+
>['tools'],
|
|
4653
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
4654
|
+
TContext,
|
|
4655
|
+
TMiddleware extends Array<unknown> | undefined,
|
|
4656
|
+
>(
|
|
4657
|
+
options: TextActivityOptionsWithContext<
|
|
4658
|
+
TAdapter,
|
|
4659
|
+
TInputSchema,
|
|
4660
|
+
TInputStream,
|
|
4661
|
+
TTools,
|
|
4662
|
+
TInterrupts,
|
|
4663
|
+
TContext,
|
|
4664
|
+
TMiddleware
|
|
4665
|
+
>,
|
|
4666
|
+
overrides: { outputSchema: TOutputSchema; stream: TOutputStream },
|
|
4667
|
+
): RuntimeTextActivityOptions<TAdapter, TOutputSchema, TOutputStream> {
|
|
4668
|
+
const { middleware, ...rest } = options
|
|
4669
|
+
return {
|
|
4670
|
+
...rest,
|
|
4671
|
+
...overrides,
|
|
4672
|
+
...(middleware === undefined
|
|
4673
|
+
? {}
|
|
4674
|
+
: { middleware: readRuntimeMiddleware(middleware) }),
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
|
|
4678
|
+
function validateInterruptDefinitions(
|
|
4679
|
+
definitions:
|
|
4680
|
+
| ReadonlyArray<InterruptDefinition<any, any, any, any>>
|
|
4681
|
+
| undefined,
|
|
4682
|
+
): void {
|
|
4683
|
+
if (!definitions) return
|
|
4684
|
+
const seen = new Set<string>()
|
|
4685
|
+
for (const definition of definitions) {
|
|
4686
|
+
if (seen.has(definition.id)) {
|
|
4687
|
+
throw new Error(`Duplicate interrupt definition id: ${definition.id}`)
|
|
4688
|
+
}
|
|
4689
|
+
seen.add(definition.id)
|
|
4690
|
+
}
|
|
3866
4691
|
}
|
|
3867
4692
|
|
|
3868
4693
|
/**
|
|
@@ -3914,8 +4739,8 @@ function publishDeliverySeams(
|
|
|
3914
4739
|
* returns, so the identity has to be minted out here and the engine reached back
|
|
3915
4740
|
* through `engineRef`, which the body fills as soon as its engine exists.
|
|
3916
4741
|
*/
|
|
3917
|
-
function runStreamingText
|
|
3918
|
-
options:
|
|
4742
|
+
function runStreamingText(
|
|
4743
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, undefined, boolean>,
|
|
3919
4744
|
): AsyncIterable<StreamChunk> {
|
|
3920
4745
|
const engineRef: DeliveryEngineRef = {}
|
|
3921
4746
|
const stream = streamTextChunks(options, engineRef)
|
|
@@ -3923,8 +4748,8 @@ function runStreamingText<TContext = unknown>(
|
|
|
3923
4748
|
return stream
|
|
3924
4749
|
}
|
|
3925
4750
|
|
|
3926
|
-
async function* streamTextChunks
|
|
3927
|
-
options:
|
|
4751
|
+
async function* streamTextChunks(
|
|
4752
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, undefined, boolean>,
|
|
3928
4753
|
engineRef: DeliveryEngineRef,
|
|
3929
4754
|
): AsyncIterable<StreamChunk> {
|
|
3930
4755
|
const { adapter, middleware, context, debug, mcp, ...textOptions } = options
|
|
@@ -3943,7 +4768,7 @@ async function* streamTextChunks<TContext = unknown>(
|
|
|
3943
4768
|
params: { ...textOptions, model, logger } as TextOptions<
|
|
3944
4769
|
Record<string, any>,
|
|
3945
4770
|
Record<string, any>,
|
|
3946
|
-
|
|
4771
|
+
any
|
|
3947
4772
|
>,
|
|
3948
4773
|
middleware,
|
|
3949
4774
|
context,
|
|
@@ -3965,19 +4790,13 @@ async function* streamTextChunks<TContext = unknown>(
|
|
|
3965
4790
|
* Run non-streaming text - collects all content and returns as a string.
|
|
3966
4791
|
* Runs the full agentic loop (if tools are provided) but returns collected text.
|
|
3967
4792
|
*/
|
|
3968
|
-
function runNonStreamingText
|
|
3969
|
-
options:
|
|
4793
|
+
function runNonStreamingText(
|
|
4794
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, undefined, false>,
|
|
3970
4795
|
): Promise<string> {
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
AnyTextAdapter,
|
|
3976
|
-
undefined,
|
|
3977
|
-
true,
|
|
3978
|
-
TContext
|
|
3979
|
-
>,
|
|
3980
|
-
)
|
|
4796
|
+
const stream = runStreamingText({
|
|
4797
|
+
...options,
|
|
4798
|
+
stream: true,
|
|
4799
|
+
})
|
|
3981
4800
|
|
|
3982
4801
|
return streamToText(stream)
|
|
3983
4802
|
}
|
|
@@ -3988,11 +4807,8 @@ function runNonStreamingText<TContext = unknown>(
|
|
|
3988
4807
|
* 2. Once complete, call adapter.structuredOutput with the conversation context
|
|
3989
4808
|
* 3. Validate and return the structured result
|
|
3990
4809
|
*/
|
|
3991
|
-
async function runAgenticStructuredOutput<
|
|
3992
|
-
|
|
3993
|
-
TContext = unknown,
|
|
3994
|
-
>(
|
|
3995
|
-
options: TextActivityOptions<AnyTextAdapter, TSchema, boolean, TContext>,
|
|
4810
|
+
async function runAgenticStructuredOutput<TSchema extends SchemaInput>(
|
|
4811
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, TSchema, boolean>,
|
|
3996
4812
|
): Promise<InferSchemaType<TSchema>> {
|
|
3997
4813
|
const {
|
|
3998
4814
|
adapter,
|
|
@@ -4063,7 +4879,7 @@ async function runAgenticStructuredOutput<
|
|
|
4063
4879
|
params: { ...textOptions, model, logger } as TextOptions<
|
|
4064
4880
|
Record<string, unknown>,
|
|
4065
4881
|
Record<string, unknown>,
|
|
4066
|
-
|
|
4882
|
+
any
|
|
4067
4883
|
>,
|
|
4068
4884
|
middleware,
|
|
4069
4885
|
context,
|
|
@@ -4126,6 +4942,15 @@ async function runAgenticStructuredOutput<
|
|
|
4126
4942
|
* Uses an `unknown`-input runtime check rather than `as` casts so the engine
|
|
4127
4943
|
* stays cast-free in its hot path.
|
|
4128
4944
|
*/
|
|
4945
|
+
function readCustomEventMessageId(value: unknown): string | undefined {
|
|
4946
|
+
if (typeof value !== 'object' || value === null) return undefined
|
|
4947
|
+
if (!('messageId' in value)) return undefined
|
|
4948
|
+
const messageId = value.messageId
|
|
4949
|
+
return typeof messageId === 'string' && messageId !== ''
|
|
4950
|
+
? messageId
|
|
4951
|
+
: undefined
|
|
4952
|
+
}
|
|
4953
|
+
|
|
4129
4954
|
function readStructuredOutputCompleteValue(
|
|
4130
4955
|
value: unknown,
|
|
4131
4956
|
): { object: unknown; raw: string; reasoning?: string } | null {
|
|
@@ -4271,11 +5096,8 @@ async function* fallbackStructuredOutputStream(
|
|
|
4271
5096
|
* synchronously at call time rather than as a yielded RUN_ERROR mid-stream —
|
|
4272
5097
|
* those are programmer errors, not runtime conditions.
|
|
4273
5098
|
*/
|
|
4274
|
-
function runStreamingStructuredOutput<
|
|
4275
|
-
|
|
4276
|
-
TContext = unknown,
|
|
4277
|
-
>(
|
|
4278
|
-
options: TextActivityOptions<AnyTextAdapter, TSchema, true, TContext>,
|
|
5099
|
+
function runStreamingStructuredOutput<TSchema extends SchemaInput>(
|
|
5100
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, TSchema, true>,
|
|
4279
5101
|
): StructuredOutputStream<InferSchemaType<TSchema>> {
|
|
4280
5102
|
const { outputSchema } = options
|
|
4281
5103
|
|
|
@@ -4336,11 +5158,8 @@ type StructuredOutputStreamInternal<T> = AsyncIterable<
|
|
|
4336
5158
|
StreamChunk | StructuredOutputCompleteEvent<T>
|
|
4337
5159
|
>
|
|
4338
5160
|
|
|
4339
|
-
async function* runStreamingStructuredOutputImpl<
|
|
4340
|
-
|
|
4341
|
-
TContext = unknown,
|
|
4342
|
-
>(
|
|
4343
|
-
options: TextActivityOptions<AnyTextAdapter, TSchema, true, TContext>,
|
|
5161
|
+
async function* runStreamingStructuredOutputImpl<TSchema extends SchemaInput>(
|
|
5162
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, TSchema, true>,
|
|
4344
5163
|
jsonSchema: NonNullable<ReturnType<typeof convertSchemaToJsonSchema>>,
|
|
4345
5164
|
normalize: (data: unknown) => unknown,
|
|
4346
5165
|
engineRef: DeliveryEngineRef,
|
|
@@ -4383,7 +5202,7 @@ async function* runStreamingStructuredOutputImpl<
|
|
|
4383
5202
|
params: { ...textOptions, model, logger } as TextOptions<
|
|
4384
5203
|
Record<string, unknown>,
|
|
4385
5204
|
Record<string, unknown>,
|
|
4386
|
-
|
|
5205
|
+
any
|
|
4387
5206
|
>,
|
|
4388
5207
|
middleware,
|
|
4389
5208
|
context,
|