feat(gateway): support per-channel and per-session queue policy overrides
This commit is contained in:
@@ -124,3 +124,62 @@ describe('createAgentHandlers command fast-path', () => {
|
||||
expect(((sent[0] as GatewayEvent).data as { content: string }).content).toBe('agent response');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAgentHandlers queue policy resolution', () => {
|
||||
it('passes resolved per-request queue policy into lane enqueue', async () => {
|
||||
const mockAgent = {
|
||||
process: vi.fn(async () => 'ok'),
|
||||
getUsage: vi.fn(() => ({
|
||||
primary: { inputTokens: 0, outputTokens: 0, calls: 0 },
|
||||
delegation: {},
|
||||
total: { inputTokens: 0, outputTokens: 0, calls: 0, estimatedCost: 0 },
|
||||
})),
|
||||
getModelTier: vi.fn(() => 'default'),
|
||||
setModelTier: vi.fn(),
|
||||
compact: vi.fn(async () => null),
|
||||
reset: vi.fn(),
|
||||
setOnToolUse: vi.fn(),
|
||||
};
|
||||
|
||||
const sessionBridge = {
|
||||
getAgent: vi.fn(() => mockAgent),
|
||||
getSessionId: vi.fn(() => 'ws:s1'),
|
||||
setBusy: vi.fn(),
|
||||
setOnToolUse: vi.fn(),
|
||||
isBusy: vi.fn(() => false),
|
||||
};
|
||||
|
||||
const laneQueue = {
|
||||
enqueue: vi.fn(async (_laneId: string, work: () => Promise<unknown>) => work()),
|
||||
cancel: vi.fn(),
|
||||
} as unknown as LaneQueue;
|
||||
|
||||
const resolveQueuePolicy = vi.fn(() => ({ mode: 'steer' as const, cap: 3 }));
|
||||
|
||||
const handlers = createAgentHandlers({
|
||||
sessionBridge: sessionBridge as unknown as AgentHandlerDeps['sessionBridge'],
|
||||
laneQueue,
|
||||
resolveQueuePolicy,
|
||||
});
|
||||
|
||||
const sent: OutboundMessage[] = [];
|
||||
const send = vi.fn((msg: OutboundMessage) => sent.push(msg));
|
||||
|
||||
await handlers['agent.send']({
|
||||
id: 1,
|
||||
method: 'agent.send',
|
||||
params: { message: 'hello', connectionId: 'conn-1' },
|
||||
}, send);
|
||||
|
||||
expect(resolveQueuePolicy).toHaveBeenCalledWith({
|
||||
laneId: 'ws:s1',
|
||||
sessionId: 'ws:s1',
|
||||
connectionId: 'conn-1',
|
||||
channel: 'ws',
|
||||
});
|
||||
expect((laneQueue.enqueue as unknown as ReturnType<typeof vi.fn>).mock.calls[0][2]).toEqual({
|
||||
mode: 'steer',
|
||||
cap: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { SendFn } from '../router.js';
|
||||
import { makeEvent, makeError, ErrorCode } from '../protocol.js';
|
||||
import type { SessionBridge } from '../session-bridge.js';
|
||||
import type { LaneQueue } from '../lane-queue.js';
|
||||
import type { LaneQueueConfig } from '../lane-queue.js';
|
||||
import type { MetricsCollector } from '../metrics.js';
|
||||
import type { Attachment } from '../../channels/types.js';
|
||||
import type { SessionManager } from '../../session/manager.js';
|
||||
@@ -14,6 +15,12 @@ import { randomUUID } from 'crypto';
|
||||
export interface AgentHandlerDeps {
|
||||
sessionBridge: SessionBridge;
|
||||
laneQueue: LaneQueue;
|
||||
resolveQueuePolicy?: (ctx: {
|
||||
laneId: string;
|
||||
sessionId?: string;
|
||||
connectionId: string;
|
||||
channel: string;
|
||||
}) => Partial<LaneQueueConfig> | undefined;
|
||||
metrics?: MetricsCollector;
|
||||
sessionManager?: SessionManager;
|
||||
commandRegistry?: CommandRegistry;
|
||||
@@ -48,6 +55,7 @@ export function createAgentHandlers(deps: AgentHandlerDeps) {
|
||||
// Falls back to connectionId if session lookup fails (shouldn't happen).
|
||||
const sessionId = deps.sessionBridge.getSessionId(connectionId);
|
||||
const laneId = sessionId ?? connectionId;
|
||||
const channel = sessionId?.split(':', 1)[0] ?? 'ws';
|
||||
|
||||
// Enqueue the work — if the lane is idle it runs immediately,
|
||||
// otherwise it waits for earlier requests on the same session to finish.
|
||||
@@ -312,7 +320,7 @@ export function createAgentHandlers(deps: AgentHandlerDeps) {
|
||||
deps.sessionBridge.setOnToolUse(connectionId, undefined);
|
||||
deps.metrics?.endRequest(requestId);
|
||||
}
|
||||
});
|
||||
}, deps.resolveQueuePolicy?.({ laneId, sessionId, connectionId, channel }));
|
||||
},
|
||||
|
||||
'agent.cancel': async (request: GatewayRequest): Promise<OutboundMessage> => {
|
||||
|
||||
Reference in New Issue
Block a user