🥸 chore: Resolve Agents SDK Path Aliases That Masked Backend Types (#15160)

* 🐛 fix: Restore Agents SDK Type Resolution in Backend Type Checks

* 🐛 fix: Preserve Typed Prompt Callback Assignability

* 🐛 fix: Accept Agents Function Tool Calls in isImageVisionTool

* 🐛 fix: Prove the Run Step Wire Contract at Compile Time
This commit is contained in:
Danny Avila 2026-08-24 08:38:30 -04:00 • committed by GitHub
parent b6e3cf46d2
commit 6a7da61234
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 241 additions and 142 deletions

View file

@ -954,7 +954,7 @@ export default function useStepHandler({
// Store tool call IDs if present
if (runStep.stepDetails.type === StepTypes.TOOL_CALLS) {
let updatedResponse = { ...response };
(runStep.stepDetails.tool_calls as Agents.ToolCall[]).forEach((toolCall) => {
((runStep.stepDetails.tool_calls ?? []) as Agents.ToolCall[]).forEach((toolCall) => {
const toolCallId = toolCall.id ?? '';
if ('id' in toolCall && toolCallId) {
toolCallIdMap.current.set(runStep.id, toolCallId);

View file

@ -139,7 +139,6 @@ liveDescribe('Graph subagent E2E (LibreChat)', () => {
{
configurable: { thread_id: runId },
recursionLimit: 100,
streamMode: 'values',
version: 'v2',
},
);

View file

@ -212,7 +212,11 @@ async function runFullTurn({
initialSummary,
runId: `e2e-${Date.now()}`,
signal: abortController.signal,
customHandlers: buildHandlers(collectedUsage, aggregateContent, spies) as never,
customHandlers: buildHandlers(
collectedUsage,
aggregateContent as (params: { event: string; data: unknown }) => void,
spies,
) as never,
summarizationConfig,
tokenCounter,
});

View file

@ -100,7 +100,7 @@ describe('createActivityPhaseWiring', () => {
phase: 'final_answer',
},
},
},
} as never,
undefined,
undefined,
);
@ -112,7 +112,7 @@ describe('createActivityPhaseWiring', () => {
delta: {
content: { type: ContentTypes.TEXT, text: 'A'.repeat(SUBSTANTIAL_TEXT_CHARS + 1) },
},
},
} as never,
undefined,
undefined,
);
@ -185,13 +185,13 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: id, content_type: 'text', ...(phase && { phase }) },
},
},
} as never,
undefined,
undefined,
);
handlers?.[GraphEvents.ON_MESSAGE_DELTA]?.handle(
GraphEvents.ON_MESSAGE_DELTA,
{ id, delta: { content: { type: ContentTypes.TEXT, text } } },
{ id, delta: { content: { type: ContentTypes.TEXT, text } } } as never,
undefined,
undefined,
);
@ -274,13 +274,16 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
handlers?.[GraphEvents.ON_MESSAGE_DELTA]?.handle(
GraphEvents.ON_MESSAGE_DELTA,
{ id: 'interleaved-text', delta: { content: { type: ContentTypes.TEXT, text: 'prefix' } } },
{
id: 'interleaved-text',
delta: { content: { type: ContentTypes.TEXT, text: 'prefix' } },
} as never,
undefined,
undefined,
);
@ -291,7 +294,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'interleaved-text',
delta: { content: { type: ContentTypes.TEXT, text: 'x'.repeat(SUBSTANTIAL_TEXT_CHARS) } },
},
} as never,
undefined,
undefined,
);
@ -348,7 +351,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -360,7 +363,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'think' },
},
},
} as never,
undefined,
undefined,
);
@ -370,7 +373,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'later-reasoning',
delta: { content: { type: ContentTypes.THINK, think: 'Investigating the later tool.' } },
},
} as never,
undefined,
undefined,
);
@ -379,7 +382,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'boundary-text',
delta: { content: { type: ContentTypes.TEXT, text: substantialText('Boundary result.') } },
},
} as never,
undefined,
undefined,
);
@ -547,14 +550,14 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
const emitTextDelta = (id: string, text: string) =>
handlers?.[GraphEvents.ON_MESSAGE_DELTA]?.handle(
GraphEvents.ON_MESSAGE_DELTA,
{ id, delta: { content: { type: ContentTypes.TEXT, text } } },
{ id, delta: { content: { type: ContentTypes.TEXT, text } } } as never,
undefined,
undefined,
);
@ -572,7 +575,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -626,7 +629,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -678,7 +681,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -736,7 +739,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'think' },
},
},
} as never,
undefined,
undefined,
);
@ -746,7 +749,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'missing-tool-reasoning',
delta: { content: { type: ContentTypes.THINK, think: repeatedReasoning } },
},
} as never,
undefined,
undefined,
);
@ -766,7 +769,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'think' },
},
},
} as never,
undefined,
undefined,
);
@ -776,7 +779,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'current-reasoning',
delta: { content: { type: ContentTypes.THINK, think: repeatedReasoning } },
},
} as never,
undefined,
undefined,
);
@ -789,7 +792,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -826,7 +829,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -858,7 +861,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'think' },
},
},
} as never,
undefined,
undefined,
);
@ -867,7 +870,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'reasoning-step',
delta: { content: { type: ContentTypes.THINK, think: 'Compared both auth paths.' } },
},
} as never,
undefined,
undefined,
);
@ -880,7 +883,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'commentary' },
},
},
} as never,
undefined,
undefined,
);
@ -907,7 +910,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -948,7 +951,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -985,7 +988,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'think' },
},
},
} as never,
undefined,
undefined,
);
@ -994,7 +997,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'lane-reasoning',
delta: { content: { type: ContentTypes.THINK, think: 'Checked the lane input.' } },
},
} as never,
undefined,
undefined,
);
@ -1009,7 +1012,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -1039,7 +1042,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -1083,7 +1086,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'think' },
},
},
} as never,
undefined,
undefined,
);
@ -1093,7 +1096,7 @@ describe('createActivityPhaseWiring', () => {
{
id,
delta: { content: { type: ContentTypes.THINK, think: text } },
},
} as never,
undefined,
undefined,
);
@ -1115,7 +1118,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -1173,7 +1176,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -1254,7 +1257,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'think' },
},
},
} as never,
undefined,
undefined,
);
@ -1266,7 +1269,7 @@ describe('createActivityPhaseWiring', () => {
delta: {
content: { type: ContentTypes.THINK, think: 'Verified one more edge case.' },
},
},
} as never,
undefined,
undefined,
);
@ -1823,7 +1826,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -1874,7 +1877,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -1924,7 +1927,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -1979,7 +1982,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2035,7 +2038,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2090,7 +2093,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2147,7 +2150,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2216,7 +2219,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2332,7 +2335,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2408,7 +2411,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2448,7 +2451,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'think' },
},
},
} as never,
undefined,
undefined,
);
@ -2485,13 +2488,13 @@ describe('createActivityPhaseWiring', () => {
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
};
handler?.handle(GraphEvents.ON_RUN_STEP, finalStep, undefined, undefined);
handler?.handle(GraphEvents.ON_RUN_STEP, finalStep as never, undefined, undefined);
await flushDetached();
expect(generatePhase).not.toHaveBeenCalled();
handler?.handle(
GraphEvents.ON_RUN_STEP,
{ ...finalStep, id: 'root-final', groupId: undefined },
{ ...finalStep, id: 'root-final', groupId: undefined } as never,
undefined,
undefined,
);
@ -2526,13 +2529,13 @@ describe('createActivityPhaseWiring', () => {
message_creation: { message_id: 'm', content_type: 'text' },
},
};
handler?.handle(GraphEvents.ON_RUN_STEP, textStep, undefined, undefined);
handler?.handle(GraphEvents.ON_RUN_STEP, textStep as never, undefined, undefined);
await flushDetached();
expect(generatePhase).not.toHaveBeenCalled();
handler?.handle(
GraphEvents.ON_RUN_STEP,
{ ...textStep, id: 'root-text', groupId: undefined },
{ ...textStep, id: 'root-text', groupId: undefined } as never,
undefined,
undefined,
);
@ -2579,7 +2582,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'commentary' },
},
},
} as never,
undefined,
undefined,
);
@ -2624,7 +2627,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2639,7 +2642,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'commentary' },
},
},
} as never,
undefined,
undefined,
);
@ -2760,7 +2763,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2778,7 +2781,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2826,7 +2829,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2880,7 +2883,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -2934,7 +2937,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -2972,7 +2975,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -3018,7 +3021,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -3027,7 +3030,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'boundary',
delta: { content: { type: ContentTypes.TEXT, text: substantialText('Boundary result.') } },
},
} as never,
undefined,
undefined,
);
@ -3123,7 +3126,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text', phase: 'final_answer' },
},
},
} as never,
undefined,
undefined,
);
@ -3132,7 +3135,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'final-step',
delta: { content: { type: ContentTypes.TEXT, text: 'Done.' } },
},
} as never,
undefined,
undefined,
);
@ -3194,7 +3197,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -3203,7 +3206,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'early-text',
delta: { content: { type: ContentTypes.TEXT, text: 'Context for the earlier work.' } },
},
} as never,
undefined,
undefined,
);
@ -3305,7 +3308,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -3319,7 +3322,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -3328,7 +3331,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'parallel-text',
delta: { content: { type: ContentTypes.TEXT, text: 'Context for the later work.' } },
},
} as never,
undefined,
undefined,
);
@ -3337,7 +3340,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'boundary',
delta: { content: { type: ContentTypes.TEXT, text: substantialText('Boundary result.') } },
},
} as never,
undefined,
undefined,
);
@ -3402,7 +3405,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -3412,7 +3415,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'parallel-text',
delta: { content: { type: ContentTypes.TEXT, text: 'Context for the later work.' } },
},
} as never,
undefined,
undefined,
);
@ -3421,7 +3424,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'boundary',
delta: { content: { type: ContentTypes.TEXT, text: substantialText('Boundary result.') } },
},
} as never,
undefined,
undefined,
);
@ -3484,7 +3487,7 @@ describe('createActivityPhaseWiring', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', content_type: 'text' },
},
},
} as never,
undefined,
undefined,
);
@ -3493,7 +3496,7 @@ describe('createActivityPhaseWiring', () => {
{
id: 'boundary',
delta: { content: { type: ContentTypes.TEXT, text: substantialText('Boundary result.') } },
},
} as never,
undefined,
undefined,
);
@ -3528,7 +3531,7 @@ describe('createAssistantPhaseStampingHandlers', () => {
type: StepTypes.MESSAGE_CREATION,
message_creation: { message_id: 'm', phase: 'commentary' },
},
},
} as never,
undefined,
undefined,
);
@ -3537,7 +3540,7 @@ describe('createAssistantPhaseStampingHandlers', () => {
{
id: 'commentary-step',
delta: { content: { type: ContentTypes.TEXT, text: 'I will compare both paths.' } },
},
} as never,
undefined,
undefined,
);

View file

@ -415,13 +415,12 @@ export function createAssistantPhaseStampingHandlers(
(part as { type?: string } | null)?.type === ContentTypes.TEXT
? { ...(part as Record<string, unknown>), phase }
: part;
const forwarded = {
...(data as Record<string, unknown>),
const forwarded = Object.assign({}, data, {
delta: {
...delta,
content: Array.isArray(raw) ? raw.map(stamp) : stamp(raw),
},
};
});
return messageHandler.handle(event, forwarded, metadata, graph);
},
};

View file

@ -324,7 +324,7 @@ type ResumableRunStep = {
export function normalizeResumeRunStepIndices<T extends ResumableRunStep>(
runSteps: readonly T[],
seedContent: readonly { type?: string; tool_call?: { id?: string } }[] = [],
seedContent: readonly ({ type?: string; tool_call?: { id?: string } } | undefined)[] = [],
): T[] {
const toolCallIndices = new Map<string, number>();
seedContent.forEach((part, index) => {
@ -361,7 +361,7 @@ export function hydrateResumeRunSteps(
runSteps: readonly RunStep[],
stepMap: Map<string, RunStep | undefined> | undefined,
graph: { toolCallStepIds?: Map<string, string> } | null | undefined,
seedContent: readonly { type?: string; tool_call?: { id?: string } }[] = [],
seedContent: readonly ({ type?: string; tool_call?: { id?: string } } | undefined)[] = [],
): void {
for (const runStep of normalizeResumeRunStepIndices(runSteps, seedContent)) {
if (!runStep?.id) {

View file

@ -91,21 +91,21 @@ function createHarness(
type: StepTypes.MESSAGE_CREATION,
message_creation: { content_type: ContentTypes.THINK },
},
},
} as never,
metadata,
);
};
const append = async (text: string, id = 'reasoning-1') => {
await wrapped[GraphEvents.ON_REASONING_DELTA].handle(
GraphEvents.ON_REASONING_DELTA,
reasoningDelta(id, text),
reasoningDelta(id, text) as never,
);
};
const close = async (id = 'reasoning-1') => {
await wrapped[GraphEvents.ON_RUN_STEP_CLOSED].handle(GraphEvents.ON_RUN_STEP_CLOSED, {
id,
status: 'completed',
});
} as never);
};
const settle = async () => {
for (let i = 0; i < 5; i += 1) {

View file

@ -1,3 +1,4 @@
import { randomUUID } from 'crypto';
import { logger } from '@librechat/data-schemas';
import { ensureHandler } from '@langchain/core/callbacks/manager';
import { Run, Providers, Constants, HookRegistry } from '@librechat/agents';
@ -1883,6 +1884,7 @@ export async function createRun({
}
const streamLimits = resolveStreamLimits(agentsEndpointConfig);
const resolvedRunId = runId ?? randomUUID();
/**
* Built as a variable (not an inline literal) so the extra
@ -1892,7 +1894,7 @@ export async function createRun({
* the field at the call site once the dependency is bumped.
*/
const runConfig = {
runId,
runId: resolvedRunId,
graphConfig,
tokenCounter,
customHandlers,
@ -1959,7 +1961,7 @@ export async function createRun({
// tracing is enabled. Requires @librechat/agents >= 3.2.21.
langfuse: buildLangfuseConfig({
appConfig,
runId,
runId: resolvedRunId,
tenantId: tenantId ?? user?.tenantId,
centralTraceExportEnabled,
}),

View file

@ -27,11 +27,26 @@ describe('createSteerIndexOffsetHandlers', () => {
);
const handler = wrapped![GraphEvents.ON_RUN_STEP];
handler.handle(GraphEvents.ON_RUN_STEP, { id: 'step-1', index: 0 }, undefined, undefined);
handler.handle(
GraphEvents.ON_RUN_STEP,
{ id: 'step-1', index: 0 } as never,
undefined,
undefined,
);
state.offset = 1;
handler.handle(GraphEvents.ON_RUN_STEP, { id: 'step-2', index: 1 }, undefined, undefined);
handler.handle(
GraphEvents.ON_RUN_STEP,
{ id: 'step-2', index: 1 } as never,
undefined,
undefined,
);
state.offset = 2;
handler.handle(GraphEvents.ON_RUN_STEP, { id: 'step-3', index: 2 }, undefined, undefined);
handler.handle(
GraphEvents.ON_RUN_STEP,
{ id: 'step-3', index: 2 } as never,
undefined,
undefined,
);
expect(calls.map((c) => (c.data as { index: number }).index)).toEqual([0, 2, 4]);
});
@ -47,14 +62,14 @@ describe('createSteerIndexOffsetHandlers', () => {
const handler = wrapped![GraphEvents.ON_AGENT_UPDATE];
handler.handle(
GraphEvents.ON_AGENT_UPDATE,
{ agent_update: { index: 3, runId: 'run-1' } },
{ agent_update: { index: 3, runId: 'run-1' } } as never,
undefined,
undefined,
);
state.offset = 2;
handler.handle(
GraphEvents.ON_AGENT_UPDATE,
{ agent_update: { index: 4, runId: 'run-1' } },
{ agent_update: { index: 4, runId: 'run-1' } } as never,
undefined,
undefined,
);
@ -79,7 +94,7 @@ describe('createSteerIndexOffsetHandlers', () => {
expect(wrapped![GraphEvents.ON_MESSAGE_DELTA]).toBe(passthrough);
wrapped![GraphEvents.ON_RUN_STEP].handle(
GraphEvents.ON_RUN_STEP,
{ id: 'step-x' },
{ id: 'step-x' } as never,
undefined,
undefined,
);
@ -99,9 +114,19 @@ describe('createSteerIndexOffsetHandlers', () => {
);
const handler = wrapped![GraphEvents.ON_RUN_STEP];
handler.handle(GraphEvents.ON_RUN_STEP, { id: 'step-1', index: 0 }, undefined, undefined);
handler.handle(
GraphEvents.ON_RUN_STEP,
{ id: 'step-1', index: 0 } as never,
undefined,
undefined,
);
state.offset = 1;
handler.handle(GraphEvents.ON_RUN_STEP, { id: 'step-2', index: 1 }, undefined, undefined);
handler.handle(
GraphEvents.ON_RUN_STEP,
{ id: 'step-2', index: 1 } as never,
undefined,
undefined,
);
// seed offset (2) applies inside; steer offset applies on top
expect(calls.map((c) => (c.data as { index: number }).index)).toEqual([2, 4]);

View file

@ -98,7 +98,7 @@ describe('createSteerDrainHook', () => {
},
});
const output: SteerDrainOutput = await hook(batchInput(), abortSignal);
const output = (await hook(batchInput(), abortSignal)) as SteerDrainOutput;
expect(applied).toEqual(['first', 'second']);
expect(output.injectedMessages).toEqual([
{ role: 'user', content: 'first', source: 'steer' },
@ -156,7 +156,7 @@ describe('createSteerDrainHook', () => {
},
});
const output: SteerDrainOutput = await hook(batchInput(), abortSignal);
const output = (await hook(batchInput(), abortSignal)) as SteerDrainOutput;
expect(output).toEqual({});
expect((await GenerationJobManager.steering.peek(streamId)).map((item) => item.text)).toEqual([
'survives',
@ -194,7 +194,7 @@ describe('createSteerDrainHook', () => {
buildMedia,
});
const output: SteerDrainOutput = await hook(batchInput(), abortSignal);
const output = (await hook(batchInput(), abortSignal)) as SteerDrainOutput;
// buildMedia is consulted only for items that carry files.
expect(buildMedia).toHaveBeenCalledTimes(1);
expect(calls).toEqual(['apply:s1', 'apply:s2', 'media:s1']);
@ -227,7 +227,7 @@ describe('createSteerDrainHook', () => {
},
});
const output: SteerDrainOutput = await hook(batchInput(), abortSignal);
const output = (await hook(batchInput(), abortSignal)) as SteerDrainOutput;
expect(appliedBeforeEncode).toBe(true);
expect(output.injectedMessages).toEqual([
{ role: 'user', content: 'must land first', source: 'steer' },
@ -251,7 +251,7 @@ describe('createSteerDrainHook', () => {
},
});
const output: SteerDrainOutput = await hook(batchInput(), abortSignal);
const output = (await hook(batchInput(), abortSignal)) as SteerDrainOutput;
expect(output.injectedMessages).toEqual([
{ role: 'user', content: 'words survive', source: 'steer' },
]);
@ -300,7 +300,7 @@ describe('createSteerPreemptBoundaryHook', () => {
},
});
const output: SteerDrainOutput = await hook(boundaryInput(), abortSignal);
const output = (await hook(boundaryInput(), abortSignal)) as SteerDrainOutput;
expect(applied).toEqual(['first', 'second']);
expect(output.injectedMessages).toEqual([
{ role: 'user', content: 'first', source: 'steer' },
@ -436,7 +436,7 @@ describe('createSteerPreemptBoundaryHook', () => {
jobCreatedAt: job.createdAt,
applySteer: jest.fn(),
});
const output: SteerDrainOutput = await hook(boundaryInput(), abortSignal);
const output = (await hook(boundaryInput(), abortSignal)) as SteerDrainOutput;
expect(output.injectedMessages).toHaveLength(1);
/** Both the drained id and the stale snapshot id are spent. */
@ -485,7 +485,7 @@ describe('createSteerPreemptBoundaryHook', () => {
},
});
const output: SteerDrainOutput = await hook(boundaryInput(), abortSignal);
const output = (await hook(boundaryInput(), abortSignal)) as SteerDrainOutput;
expect(output).toEqual({});
expect((await GenerationJobManager.steering.peek(streamId)).map((item) => item.text)).toEqual([
'still injected',

View file

@ -1146,7 +1146,7 @@ export class SubagentThreadTaskStore extends InMemorySubagentTaskStore {
const snapshot = this.get(scopeId, taskId);
const lockKey = snapshot?.threadId == null ? undefined : `${scopeId}\u0000${snapshot.threadId}`;
const lease = lockKey == null ? undefined : this.activeThreads.get(lockKey);
if (lease?.taskId === taskId && lease.settling) {
if (snapshot != null && lease?.taskId === taskId && lease.settling) {
return { status: 'not_running', task: snapshot };
}
const result = super.control(scopeId, taskId, command);

View file

@ -31,6 +31,7 @@ import {
PAUSE_PERSISTENCE_TIMEOUT_ERROR,
PAUSE_PERSISTENCE_TIMEOUT_MS,
isPendingActionStale,
toWireRunSteps,
} from '~/stream/interfaces/IJobStore';
import {
isRecoveredSteerPayload,
@ -1466,7 +1467,7 @@ export class InMemoryJobStore implements IJobStoreV2 {
// Dereference WeakRef - may return undefined if GC'd
const graph = state.graphRef.deref();
return graph?.contentData ?? [];
return toWireRunSteps(graph?.contentData ?? []);
}
/**

View file

@ -34,6 +34,7 @@ import {
PAUSE_PERSISTENCE_TIMEOUT_ERROR,
PAUSE_PERSISTENCE_TIMEOUT_MS,
isPendingActionStale,
toWireRunSteps,
} from '~/stream/interfaces/IJobStore';
import {
MAX_COALESCED_BYTES,
@ -3661,7 +3662,7 @@ export class RedisJobStore implements IJobStoreV2 {
g.getRunSteps(),
);
if (localSteps && localSteps.length > 0) {
return localSteps;
return toWireRunSteps(localSteps);
}
}
// Note: Don't delete from cache here - graph may still be valid

View file

@ -4,12 +4,63 @@ import type {
TPendingSteer,
UserSubmittedMessageFieldPath,
} from 'librechat-data-provider';
import type { StandardGraph } from '@librechat/agents';
import type { RunStep, StandardGraph } from '@librechat/agents';
import type { ActivityPhaseSnapshot } from '~/agents/activityPhases/runtime';
import type { ResolvedAskUserQuestion } from '~/agents/hitl/resume';
import type { RecoveredSteerPayload } from '../SteerRecovery';
import type { MCPRuntimeRequestBody } from '~/mcp/types';
/**
* Rewrites string-enum members to their literal values, recursively. The SDK and
* data-provider declare nominally distinct enums (`ContentTypes`, `StepTypes`, ...)
* with identical string values; erasing that nominality is what lets the two run-step
* contracts be compared structurally.
*/
type WireShape<T> = T extends string
? `${T}`
: T extends readonly (infer U)[]
? WireShape<U>[]
: T extends object
? { [K in keyof T]: WireShape<T[K]> }
: T;
type StaticAssert<T extends true> = T;
/**
* Compile-time proof that the SDK run step and the wire contract (`Agents.RunStep`)
* agree structurally once enum nominality is erased: any added, removed, retyped, or
* newly optional SDK field fails these assertions, so drift cannot silently enter
* resume state through `toWireRunSteps`.
*
* `summary.content` is the one deliberately unchecked field: the SDK reuses its full
* `MessageContentComplex` union there, while the wire contract narrows it to the plain
* text blocks summarization actually emits. That narrowing is the single semantic
* judgment this conversion vouches for.
*/
type _WireRunStepContractHolds = StaticAssert<
WireShape<Omit<RunStep, 'summary'>> extends WireShape<Omit<Agents.RunStep, 'summary'>>
? true
: false
>;
type _WireSummaryContractHolds = StaticAssert<
WireShape<Omit<NonNullable<RunStep['summary']>, 'content'>> extends WireShape<
Omit<NonNullable<Agents.RunStep['summary']>, 'content'>
>
? true
: false
>;
/**
* Run steps living on the SDK graph serialize to exactly the wire shape
* `Agents.RunStep` describes; the assertion is safe because
* `_WireRunStepContractHolds` above proves the contracts identical modulo the
* nominally-split enums, which share their string values at runtime.
*/
export function toWireRunSteps(steps: readonly RunStep[]): Agents.RunStep[] {
return steps as Agents.RunStep[];
}
/**
* A pause owner has this long to durably persist the interrupted turn before
* the barrier is considered abandoned. An abandoned barrier must fail closed:

View file

@ -1,16 +0,0 @@
/**
* `@librechat/agents` publishes its declaration files with its internal `@/*` path aliases
* unrewritten, so a consumer cannot resolve them. `types/llm.d.ts` imports `Providers` that
* way, which leaves `ProviderOptionsMap`'s computed keys unresolved and collapses
* `keyof ProviderOptionsMap` to `number`. Until v3.6.16 that only degraded `LLMConfig`
* silently; v3.6.16 made `SharedLLMConfig` generic over that key union, so `provider` became
* `number | RuntimeProviderName` and no real provider was assignable to it.
*
* Declaring the one alias `llm.d.ts` needs restores the enum, and with it the provider key
* union. Remove this once the SDK ships declarations with its aliases resolved — note that
* mapping every `@/*` alias instead unmasks a large backlog of latent errors elsewhere in
* this package, so widening it is a separate cleanup rather than a drop-in improvement.
*/
declare module '@/common' {
export { Providers } from '@librechat/agents';
}

View file

@ -19,7 +19,11 @@
"noEmit": true,
"sourceMap": true,
"paths": {
"~/*": ["./src/*"]
"~/*": ["./src/*"],
// @librechat/agents ships declaration files that import its internal "@/" alias, which
// package exports cannot map; resolve them into its published types until the SDK
// rewrites the aliases at build time.
"@/*": ["../../node_modules/@librechat/agents/dist/types/*"]
}
},
"ts-node": {

View file

@ -1,10 +1,5 @@
import { z } from 'zod';
import type {
TMessageContentParts,
AgentSubagentGraph,
FunctionToolCall,
FunctionTool,
} from './types/assistants';
import type { TMessageContentParts, AgentSubagentGraph, FunctionTool } from './types/assistants';
import type { SearchResultData } from './types/web';
import type { TFile } from './types/files';
import { userSubmittedMessageFieldPathSchema } from './filters';
@ -391,7 +386,9 @@ export const ImageVisionTool: FunctionTool = {
},
};
export const isImageVisionTool = (tool: FunctionTool | FunctionToolCall) =>
/** Structural on purpose: accepts assistants tools/tool calls and agents function tool
* calls alike — the check only ever reads `type` and `function.name`. */
export const isImageVisionTool = (tool: { type?: string; function?: { name?: string } }) =>
tool.type === 'function' && tool.function?.name === ImageVisionTool.function?.name;
export const openAISettings = {

View file

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-namespace */
import type { TTokenUsageEvent, TContextUsageEvent, TPendingSteer } from './runs';
import type { FunctionToolCall, SummaryContentPart } from './assistants';
import type { TAttachment, TPlugin } from 'src/schemas';
import type { SummaryContentPart } from './assistants';
import { StepTypes, ContentTypes, ToolCallTypes } from './runs';
export namespace Agents {
@ -67,8 +67,9 @@ export namespace Agents {
* A call to a tool.
*/
export type ToolCall = {
/** Type ("tool_call") according to Assistants Tool Call Structure */
type: ToolCallTypes.TOOL_CALL;
/** Type ("tool_call") according to Assistants Tool Call Structure; optional literal
* form included to mirror langchain's ToolCall, whose `type` is optional. */
type?: ToolCallTypes.TOOL_CALL | 'tool_call';
/** The name of the tool to be called */
name: string;
@ -203,7 +204,8 @@ export namespace Agents {
groupId?: number; // #new
stepDetails: StepDetails;
summary?: SummaryContentPart;
usage: null | object;
/** Optional to mirror the agents SDK, which omits usage until a step reports it. */
usage?: null | object;
/** Epoch ms the step was opened. Emitted by `@librechat/agents` >= 3.4.6. */
created_at?: number;
status?: RunStepStatus;
@ -324,7 +326,7 @@ export namespace Agents {
};
export type ToolCallsDetails = {
type: StepTypes.TOOL_CALLS;
tool_calls: AgentToolCall[];
tool_calls?: AgentToolCall[];
};
export type ToolCallDelta = {
type: StepTypes.TOOL_CALLS | string;
@ -338,7 +340,22 @@ export namespace Agents {
description?: string;
};
};
export type AgentToolCall = FunctionToolCall | ToolCall;
/**
* Mirrors the agents SDK's function tool-call variant: `arguments` may arrive as a
* parsed object, and `output` is attached by LibreChat aggregation only once available
* (the legacy assistants `FunctionToolCall` requires both as string/present).
*/
export type AgentFunctionToolCall = {
id: string;
type: 'function';
function: {
name: string;
arguments: string | object;
output?: string | null;
};
};
export type AgentToolCall = AgentFunctionToolCall | ToolCall;
/**
* Human-in-the-loop interrupt categories. The discriminator on
@ -734,8 +751,20 @@ export type GraphEdge = {
*
* For handoff edges: Description for the input parameter that the handoff tool accepts,
* allowing the supervisor to pass specific instructions/context to the transferred agent.
*
* The callback receives a minimal structural view of the run's messages (data-provider
* cannot depend on langchain's BaseMessage): every langchain message satisfies
* `{ content: unknown }`, and callbacks typed against richer structural message shapes
* remain assignable. The promise branch mirrors the agents SDK signature exactly —
* widening it (e.g. to `Promise<string | undefined>`) would break assignability of
* stored edges into the SDK's `GraphEdge`.
*/
prompt?: string | ((messages: BaseMessage[], runStartIndex: number) => string | undefined);
prompt?:
| string
| ((
messages: { content: unknown }[],
runStartIndex: number,
) => string | Promise<string> | undefined);
/**
* When true, excludes messages from startIndex when adding prompt.
* Automatically set to true when {results} variable is used in prompt.