@ai-sdk/harness 1.0.38 → 1.0.40
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/CHANGELOG.md +17 -0
- package/dist/agent/index.d.ts +19 -4
- package/dist/agent/index.js +189 -57
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.js +8 -1
- package/dist/bridge/index.js.map +1 -1
- package/dist/utils/index.d.ts +5 -1
- package/dist/utils/index.js +37 -5
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -2
- package/src/agent/harness-agent-session.ts +38 -8
- package/src/agent/harness-agent-settings.ts +25 -1
- package/src/agent/harness-agent.ts +17 -2
- package/src/agent/internal/harness-stream-text-result.ts +27 -0
- package/src/agent/internal/run-prompt.ts +144 -42
- package/src/agent/internal/turn-telemetry.ts +18 -4
- package/src/bridge/index.ts +11 -1
- package/src/utils/sandbox-channel.ts +61 -3
|
@@ -75,6 +75,30 @@ type Listener<TOut extends { type: string }, T extends EventTypeOf<TOut>> = (
|
|
|
75
75
|
event: Extract<TOut, { type: T }>,
|
|
76
76
|
) => void;
|
|
77
77
|
|
|
78
|
+
/*
|
|
79
|
+
* The agent and utilities entrypoints bundle this module separately. A global
|
|
80
|
+
* symbol lets the agent recognize metadata attached by the channel's bundle
|
|
81
|
+
* copy, while the non-enumerable property leaves protocol payloads unchanged.
|
|
82
|
+
*/
|
|
83
|
+
const sandboxChannelEventCheckpointSymbol = Symbol.for(
|
|
84
|
+
'vercel.ai.harness.sandboxChannelEventCheckpoint',
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
type SandboxChannelEventCheckpoint = {
|
|
88
|
+
pin: () => () => void;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export function pinSandboxChannelEventCheckpoint(
|
|
92
|
+
event: unknown,
|
|
93
|
+
): (() => void) | undefined {
|
|
94
|
+
if (event == null || typeof event !== 'object') return undefined;
|
|
95
|
+
return (
|
|
96
|
+
event as {
|
|
97
|
+
[sandboxChannelEventCheckpointSymbol]?: SandboxChannelEventCheckpoint;
|
|
98
|
+
}
|
|
99
|
+
)[sandboxChannelEventCheckpointSymbol]?.pin();
|
|
100
|
+
}
|
|
101
|
+
|
|
78
102
|
const sleep = (ms: number): Promise<void> =>
|
|
79
103
|
new Promise(resolve => {
|
|
80
104
|
const t = setTimeout(resolve, ms);
|
|
@@ -136,6 +160,9 @@ export class SandboxChannel<
|
|
|
136
160
|
* replayed to the next process on `resume`.
|
|
137
161
|
*/
|
|
138
162
|
private suspended = false;
|
|
163
|
+
private pinnedSuspensionCursor:
|
|
164
|
+
| { eventId: number; token: object }
|
|
165
|
+
| undefined;
|
|
139
166
|
/** Channel is fully torn down; `send` throws and `onClose` has fired. */
|
|
140
167
|
private terminal = false;
|
|
141
168
|
private _lastSeenEventId = 0;
|
|
@@ -251,6 +278,9 @@ export class SandboxChannel<
|
|
|
251
278
|
}
|
|
252
279
|
|
|
253
280
|
interrupt(options?: { timeoutMs?: number }): Promise<void> {
|
|
281
|
+
if (this.pinnedSuspensionCursor != null) {
|
|
282
|
+
return Promise.resolve();
|
|
283
|
+
}
|
|
254
284
|
const timeoutMs = options?.timeoutMs ?? 5000;
|
|
255
285
|
return new Promise<void>((resolve, reject) => {
|
|
256
286
|
let settled = false;
|
|
@@ -313,19 +343,24 @@ export class SandboxChannel<
|
|
|
313
343
|
* aborts it) and accumulates events past the cursor for the next process to
|
|
314
344
|
* `resume`. Unlike {@link close}, the consumer's active turn is wound down
|
|
315
345
|
* cleanly — adapters distinguish a suspend from an unexpected drop via the
|
|
316
|
-
* `'suspended'` close reason and resolve `done` successfully.
|
|
346
|
+
* `'suspended'` close reason and resolve `done` successfully. When an event
|
|
347
|
+
* checkpoint is pinned, the returned cursor points to that event so any
|
|
348
|
+
* already-dispatched tail is replayed by the next process.
|
|
317
349
|
*/
|
|
318
350
|
suspend(): Promise<number> {
|
|
319
351
|
return new Promise<number>(resolve => {
|
|
352
|
+
const pinnedSuspensionCursor = this.pinnedSuspensionCursor?.eventId;
|
|
320
353
|
if (this.terminal) {
|
|
321
|
-
resolve(this._lastSeenEventId);
|
|
354
|
+
resolve(pinnedSuspensionCursor ?? this._lastSeenEventId);
|
|
322
355
|
return;
|
|
323
356
|
}
|
|
324
357
|
// Stop counting/dispatching further inbound frames immediately, and
|
|
325
358
|
// suppress reconnect so the socket close finalises.
|
|
326
359
|
this.suspended = true;
|
|
327
360
|
this.closing = true;
|
|
328
|
-
this.onClose(() =>
|
|
361
|
+
this.onClose(() =>
|
|
362
|
+
resolve(pinnedSuspensionCursor ?? this._lastSeenEventId),
|
|
363
|
+
);
|
|
329
364
|
// Queue the close behind any already-dispatched frames so everything
|
|
330
365
|
// delivered to the consumer is reflected in the final cursor.
|
|
331
366
|
this.enqueue(() => {
|
|
@@ -465,6 +500,9 @@ export class SandboxChannel<
|
|
|
465
500
|
schema: this.outboundSchema,
|
|
466
501
|
});
|
|
467
502
|
if (validated.success) {
|
|
503
|
+
if (seq !== undefined) {
|
|
504
|
+
this.attachEventCheckpoint({ event: validated.value, eventId: seq });
|
|
505
|
+
}
|
|
468
506
|
this.dispatch(validated.value);
|
|
469
507
|
} else {
|
|
470
508
|
this.dispatch({
|
|
@@ -506,6 +544,26 @@ export class SandboxChannel<
|
|
|
506
544
|
}
|
|
507
545
|
}
|
|
508
546
|
|
|
547
|
+
private attachEventCheckpoint(options: {
|
|
548
|
+
event: TOut;
|
|
549
|
+
eventId: number;
|
|
550
|
+
}): void {
|
|
551
|
+
if (!Object.isExtensible(options.event)) return;
|
|
552
|
+
Object.defineProperty(options.event, sandboxChannelEventCheckpointSymbol, {
|
|
553
|
+
value: {
|
|
554
|
+
pin: () => {
|
|
555
|
+
const token = {};
|
|
556
|
+
this.pinnedSuspensionCursor = { eventId: options.eventId, token };
|
|
557
|
+
return () => {
|
|
558
|
+
if (this.pinnedSuspensionCursor?.token === token) {
|
|
559
|
+
this.pinnedSuspensionCursor = undefined;
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
},
|
|
563
|
+
} satisfies SandboxChannelEventCheckpoint,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
509
567
|
private finalizeClose(code: number, reason: string): void {
|
|
510
568
|
if (this.terminal) return;
|
|
511
569
|
this.terminal = true;
|