fix(agents): preserve acp and openai wrapper defaults

This commit is contained in:
Peter Steinberger
2026-04-04 14:02:42 +09:00
parent bc8048250e
commit cff8b5bebd
5 changed files with 87 additions and 11 deletions

View File

@@ -519,7 +519,7 @@ describe("spawnAcpDirect", () => {
agentTo: "room:!room:example",
},
);
expect(result.status).toBe("accepted");
expect(result.status, JSON.stringify(result)).toBe("accepted");
expect(hoisted.sessionBindingBindMock).toHaveBeenCalledWith(
expect.objectContaining({
placement: "child",
@@ -533,7 +533,7 @@ describe("spawnAcpDirect", () => {
expectAgentGatewayCall({
deliver: true,
channel: "matrix",
to: "room:!room:example",
to: "channel:child-thread",
threadId: "child-thread",
});
});
@@ -576,7 +576,7 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.status, JSON.stringify(result)).toBe("accepted");
expect(hoisted.sessionBindingBindMock).toHaveBeenCalledWith(
expect.objectContaining({
placement: "current",

View File

@@ -19,7 +19,6 @@ import {
import {
formatThreadBindingDisabledError,
formatThreadBindingSpawnDisabledError,
requiresNativeThreadContextForThreadHere,
resolveThreadBindingIdleTimeoutMsForChannel,
resolveThreadBindingMaxAgeMsForChannel,
resolveThreadBindingSpawnPolicy,
@@ -172,6 +171,59 @@ type AcpSpawnBootstrapDeliveryPlan = {
threadId?: string;
};
function resolvePlacementWithoutChannelPlugin(params: {
channel: string;
capabilities: { placements: Array<"current" | "child"> };
}): "current" | "child" {
switch (params.channel) {
case "discord":
case "matrix":
return params.capabilities.placements.includes("child") ? "child" : "current";
case "line":
case "telegram":
return "current";
}
return params.capabilities.placements.includes("child") ? "child" : "current";
}
function normalizeLineConversationIdFallback(value: string | undefined): string | undefined {
const trimmed = value?.trim() ?? "";
if (!trimmed) {
return undefined;
}
const normalized = trimmed.match(/^line:(?:(?:user|group|room):)?(.+)$/i)?.[1]?.trim() ?? trimmed;
return normalized ? normalized : undefined;
}
function normalizeTelegramConversationIdFallback(params: {
to?: string;
threadId?: string | number;
groupId?: string;
}): string | undefined {
const explicitGroupId = params.groupId?.trim();
const explicitThreadId =
params.threadId != null ? String(params.threadId).trim() || undefined : undefined;
if (
explicitGroupId &&
explicitThreadId &&
/^-?\d+$/.test(explicitGroupId) &&
/^\d+$/.test(explicitThreadId)
) {
return `${explicitGroupId}:topic:${explicitThreadId}`;
}
const trimmed = params.to?.trim() ?? "";
if (!trimmed) {
return undefined;
}
const normalized = trimmed.replace(/^telegram:(?:group:|channel:|direct:)?/i, "");
const topicMatch = /^(-?\d+):topic:(\d+)$/i.exec(normalized);
if (topicMatch?.[1] && topicMatch[2]) {
return `${topicMatch[1]}:topic:${topicMatch[2]}`;
}
return /^-?\d+$/.test(normalized) ? normalized : undefined;
}
function resolveSpawnMode(params: {
requestedMode?: SpawnAcpMode;
threadRequested: boolean;
@@ -367,6 +419,7 @@ function resolveConversationIdForThreadBinding(params: {
}): string | undefined {
const channel = params.channel?.trim().toLowerCase();
const normalizedChannelId = channel ? normalizeChannelId(channel) : null;
const channelKey = normalizedChannelId ?? channel ?? null;
const pluginResolvedConversationId = normalizedChannelId
? getChannelPlugin(normalizedChannelId)?.messaging?.resolveInboundConversation?.({
to: params.groupId ?? params.to,
@@ -378,6 +431,18 @@ function resolveConversationIdForThreadBinding(params: {
if (pluginResolvedConversationId?.trim()) {
return pluginResolvedConversationId.trim();
}
if (channelKey === "line") {
const lineConversationId = normalizeLineConversationIdFallback(params.groupId ?? params.to);
if (lineConversationId) {
return lineConversationId;
}
}
if (channelKey === "telegram") {
const telegramConversationId = normalizeTelegramConversationIdFallback(params);
if (telegramConversationId) {
return telegramConversationId;
}
}
const genericConversationId = resolveConversationIdFromTargets({
threadId: params.threadId,
targets: [params.to],
@@ -466,11 +531,18 @@ function prepareAcpThreadBinding(params: {
error: `Thread bindings are unavailable for ${policy.channel}.`,
};
}
const placement = requiresNativeThreadContextForThreadHere(policy.channel) ? "child" : "current";
if (!capabilities.bindSupported || !capabilities.placements.includes(placement)) {
const pluginPlacement = getChannelPlugin(policy.channel)?.conversationBindings
?.defaultTopLevelPlacement;
const placementToUse =
pluginPlacement ??
resolvePlacementWithoutChannelPlugin({
channel: policy.channel,
capabilities,
});
if (!capabilities.bindSupported || !capabilities.placements.includes(placementToUse)) {
return {
ok: false,
error: `Thread bindings do not support ${placement} placement for ${policy.channel}.`,
error: `Thread bindings do not support ${placementToUse} placement for ${policy.channel}.`,
};
}
const conversationIdRaw = resolveConversationIdForThreadBinding({
@@ -491,7 +563,7 @@ function prepareAcpThreadBinding(params: {
binding: {
channel: policy.channel,
accountId: policy.accountId,
placement,
placement: placementToUse,
conversationId: conversationIdRaw,
},
};

View File

@@ -80,8 +80,8 @@ function stripDisabledOpenAIReasoningPayload(payloadObj: Record<string, unknown>
return;
}
// GPT-5 models reject `reasoning.effort: "none"`. Treat the disabled effort
// as "reasoning omitted" instead of forwarding an unsupported value.
// Proxy/OpenAI-compat routes can reject `reasoning.effort: "none"`. Treat the
// disabled effort as "reasoning omitted" instead of forwarding an unsupported value.
const reasoningObj = reasoning as Record<string, unknown>;
if (reasoningObj.effort === "none") {
delete payloadObj.reasoning;

View File

@@ -735,7 +735,9 @@ describe("applyExtraParamsToAgent", () => {
expect(payloads).toHaveLength(1);
expect(payloads[0]).toEqual({
context_management: [{ type: "compaction", compact_threshold: 80000 }],
reasoning: { effort: "none", summary: "auto" },
store: true,
});
});

View File

@@ -416,7 +416,6 @@ export function applyExtraParamsToAgent(
override,
};
applyPrePluginStreamWrappers(wrapperContext);
const providerStreamBase = agent.streamFn;
const pluginWrappedStreamFn = providerRuntimeDeps.wrapProviderStreamFn({
provider,
@@ -432,6 +431,9 @@ export function applyExtraParamsToAgent(
},
});
agent.streamFn = pluginWrappedStreamFn ?? providerStreamBase;
// Apply caller/config extra params outside provider defaults so explicit values
// like `openaiWsWarmup=false` can override provider-added defaults.
applyPrePluginStreamWrappers(wrapperContext);
const providerWrapperHandled =
pluginWrappedStreamFn !== undefined && pluginWrappedStreamFn !== providerStreamBase;
applyPostPluginStreamWrappers({