🍵 feat: Continue Late Steers in Warm Agent Runs (#15357)

* feat: continue late steers in warm agent runs

* chore: bump agents sdk to v3.7.9

* fix: guard terminal steer admission
This commit is contained in:
Danny Avila 2026-08-30 11:54:17 -04:00 committed by GitHub
parent 180b56531a
commit cd3768ed1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 624 additions and 18 deletions

View file

@ -1,14 +1,20 @@
const AgentClient = require('../client');
const { isSteeringSupported, isSteerPreemptSupported } = require('@librechat/api');
const {
isSteeringSupported,
isSteerPreemptSupported,
isSteerTerminalContinuationSupported,
} = require('@librechat/api');
jest.mock('@librechat/api', () => ({
...jest.requireActual('@librechat/api'),
isSteeringSupported: jest.fn(() => true),
isSteerPreemptSupported: jest.fn(() => true),
isSteerTerminalContinuationSupported: jest.fn(() => true),
}));
const mockIsSteeringSupported = isSteeringSupported;
const mockIsPreemptSupported = isSteerPreemptSupported;
const mockIsTerminalContinuationSupported = isSteerTerminalContinuationSupported;
/** Minimal `this` for the wiring builder — it only reads these three. */
function buildWiring(streamId, { jobCreatedAt = 1700000000000 } = {}) {
@ -25,6 +31,7 @@ describe('AgentClient.buildSteerWiring — preempt capability gating', () => {
jest.clearAllMocks();
mockIsSteeringSupported.mockReturnValue(true);
mockIsPreemptSupported.mockReturnValue(true);
mockIsTerminalContinuationSupported.mockReturnValue(true);
});
it('returns both boundary hooks and the poll when preempt is supported', () => {
@ -33,6 +40,16 @@ describe('AgentClient.buildSteerWiring — preempt capability gating', () => {
expect(typeof wiring.hook).toBe('function');
expect(typeof wiring.preemptHook).toBe('function');
expect(typeof wiring.preemption?.shouldPreempt).toBe('function');
expect(typeof wiring.terminalHook).toBe('function');
});
it('omits only terminal continuation when the SDK lacks Stop continuation', () => {
mockIsTerminalContinuationSupported.mockReturnValue(false);
const wiring = buildWiring('stream-terminal-unsupported');
expect(typeof wiring.hook).toBe('function');
expect(typeof wiring.preemptHook).toBe('function');
expect(wiring.terminalHook).toBeUndefined();
});
/**

View file

@ -51,6 +51,7 @@ jest.mock('@librechat/api', () => ({
exemptFromConcurrencyLimiter: jest.fn(() => false),
toPendingSteer: jest.fn((item) => item),
isSteerPreemptSupported: jest.fn(() => true),
isSteerTerminalContinuationSupported: jest.fn(() => false),
buildRecoveredSteerPayload: jest.fn(() => null),
deleteAgentCheckpoint: jest.fn(),
getViolationInfo: jest.fn(() => ({

View file

@ -254,6 +254,7 @@ jest.mock('@librechat/api', () => ({
/** Recorded onto the job so the steer route can honour the OWNING replica's
* seal capability rather than its own probe. */
isSteerPreemptSupported: jest.fn(() => true),
isSteerTerminalContinuationSupported: jest.fn(() => false),
buildRecoveredSteerPayload: jest.fn((text, files) => {
if (typeof text !== 'string' || (files != null && !Array.isArray(files))) {
return null;

View file

@ -132,6 +132,7 @@ jest.mock('@librechat/api', () => ({
decrementPendingRequest: (...args) => mockDecrementPendingRequest(...args),
checkAndIncrementPendingRequest: (...args) => mockCheckAndIncrementPendingRequest(...args),
isSteerPreemptSupported: jest.fn(() => true),
isSteerTerminalContinuationSupported: jest.fn(() => false),
createMCPRuntimeRequestBody: ({ messageId, conversationId, parentMessageId }) => ({
messageId,
conversationId,

View file

@ -65,9 +65,11 @@ const {
createSteerIndexOffsetHandlers,
createSteerDrainHook,
createSteerPreemptBoundaryHook,
createSteerTerminalContinuationHook,
createSteerPreemptPoll,
isSteeringSupported,
isSteerPreemptSupported,
isSteerTerminalContinuationSupported,
buildSteerMedia,
collectSteerStampTargets,
stampSteerPartMedia,
@ -557,9 +559,9 @@ class AgentClient extends BaseClient {
/**
* The `steering` fragment for `createRun`: the run-scoped PostToolBatch
* drain hook plus, when the SDK can seal mid-stream, the PreemptBoundary
* twin and the preempt poll built from the SAME drain closures, so both
* boundaries inject byte-identical shapes. `undefined` when there is no
* drain hook plus the capability-gated PreemptBoundary and terminal Stop
* twins built from the SAME drain closures, so every boundary injects
* byte-identical shapes. `undefined` when there is no
* resumable job surface or the installed SDK cannot inject hook messages
* (draining would drop them).
*
@ -592,6 +594,9 @@ class AgentClient extends BaseClient {
preemptHook: createSteerPreemptBoundaryHook(drainOptions),
preemption: createSteerPreemptPoll(streamId),
}),
...(isSteerTerminalContinuationSupported() && {
terminalHook: createSteerTerminalContinuationHook(drainOptions),
}),
};
}