import { beforeEach, describe, expect, it, vi } from "vitest"; import type { FollowupRun } from "./queue.js"; const hoisted = vi.hoisted(() => { const resolveRunModelFallbacksOverrideMock = vi.fn(); return { resolveRunModelFallbacksOverrideMock }; }); vi.mock("../../agents/agent-scope.js", () => ({ resolveRunModelFallbacksOverride: (...args: unknown[]) => hoisted.resolveRunModelFallbacksOverrideMock(...args), })); const { buildThreadingToolContext, buildEmbeddedRunBaseParams, buildEmbeddedRunContexts, resolveModelFallbackOptions, resolveProviderScopedAuthProfile, } = await import("./agent-runner-utils.js"); function makeRun(overrides: Partial = {}): FollowupRun["run"] { return { sessionId: "session-1", agentId: "agent-1", config: { models: { providers: {} } }, provider: "openai", model: "gpt-4.1", agentDir: "/tmp/agent", sessionKey: "agent:test:session", sessionFile: "/tmp/session.json", workspaceDir: "/tmp/workspace", skillsSnapshot: [], ownerNumbers: ["+15550001"], enforceFinalTag: false, thinkLevel: "medium", verboseLevel: "off", reasoningLevel: "none", execOverrides: {}, bashElevated: false, timeoutMs: 60_000, ...overrides, } as unknown as FollowupRun["run"]; } describe("agent-runner-utils", () => { beforeEach(() => { hoisted.resolveRunModelFallbacksOverrideMock.mockClear(); }); it("resolves model fallback options from run context", () => { hoisted.resolveRunModelFallbacksOverrideMock.mockReturnValue(["fallback-model"]); const run = makeRun(); const resolved = resolveModelFallbackOptions(run); expect(hoisted.resolveRunModelFallbacksOverrideMock).toHaveBeenCalledWith({ cfg: run.config, agentId: run.agentId, sessionKey: run.sessionKey, }); expect(resolved).toEqual({ cfg: run.config, provider: run.provider, model: run.model, agentDir: run.agentDir, fallbacksOverride: ["fallback-model"], }); }); it("passes through missing agentId for helper-based fallback resolution", () => { hoisted.resolveRunModelFallbacksOverrideMock.mockReturnValue(["fallback-model"]); const run = makeRun({ agentId: undefined }); const resolved = resolveModelFallbackOptions(run); expect(hoisted.resolveRunModelFallbacksOverrideMock).toHaveBeenCalledWith({ cfg: run.config, agentId: undefined, sessionKey: run.sessionKey, }); expect(resolved.fallbacksOverride).toEqual(["fallback-model"]); }); it("builds embedded run base params with auth profile and run metadata", () => { const run = makeRun({ enforceFinalTag: true }); const authProfile = resolveProviderScopedAuthProfile({ provider: "openai", primaryProvider: "openai", authProfileId: "profile-openai", authProfileIdSource: "user", }); const resolved = buildEmbeddedRunBaseParams({ run, provider: "openai", model: "gpt-4.1-mini", runId: "run-1", authProfile, }); expect(resolved).toMatchObject({ sessionFile: run.sessionFile, workspaceDir: run.workspaceDir, agentDir: run.agentDir, config: run.config, skillsSnapshot: run.skillsSnapshot, ownerNumbers: run.ownerNumbers, enforceFinalTag: true, provider: "openai", model: "gpt-4.1-mini", authProfileId: "profile-openai", authProfileIdSource: "user", thinkLevel: run.thinkLevel, verboseLevel: run.verboseLevel, reasoningLevel: run.reasoningLevel, execOverrides: run.execOverrides, bashElevated: run.bashElevated, timeoutMs: run.timeoutMs, runId: "run-1", }); }); it("builds embedded contexts and scopes auth profile by provider", () => { const run = makeRun({ authProfileId: "profile-openai", authProfileIdSource: "auto", }); const resolved = buildEmbeddedRunContexts({ run, sessionCtx: { Provider: "OpenAI", To: "channel-1", SenderId: "sender-1", }, hasRepliedRef: undefined, provider: "anthropic", }); expect(resolved.authProfile).toEqual({ authProfileId: undefined, authProfileIdSource: undefined, }); expect(resolved.embeddedContext).toMatchObject({ sessionId: run.sessionId, sessionKey: run.sessionKey, agentId: run.agentId, messageProvider: "openai", messageTo: "channel-1", }); expect(resolved.senderContext).toEqual({ senderId: "sender-1", senderName: undefined, senderUsername: undefined, senderE164: undefined, }); }); it("prefers OriginatingChannel over Provider for messageProvider", () => { const run = makeRun(); const resolved = buildEmbeddedRunContexts({ run, sessionCtx: { Provider: "heartbeat", OriginatingChannel: "Telegram", OriginatingTo: "268300329", }, hasRepliedRef: undefined, provider: "openai", }); expect(resolved.embeddedContext.messageProvider).toBe("telegram"); expect(resolved.embeddedContext.messageTo).toBe("268300329"); }); it("uses OriginatingTo for threading tool context on telegram native commands", () => { const context = buildThreadingToolContext({ sessionCtx: { Provider: "telegram", To: "slash:8460800771", OriginatingChannel: "telegram", OriginatingTo: "telegram:-1003841603622", MessageThreadId: 928, MessageSid: "2284", }, config: { channels: { telegram: { allowFrom: ["*"] } } }, hasRepliedRef: undefined, }); expect(context).toMatchObject({ currentChannelId: "telegram:-1003841603622", currentThreadTs: "928", currentMessageId: "2284", }); }); it("uses OriginatingTo for threading tool context on discord native commands", () => { const context = buildThreadingToolContext({ sessionCtx: { Provider: "discord", To: "slash:1177378744822943744", OriginatingChannel: "discord", OriginatingTo: "channel:123456789012345678", MessageSid: "msg-9", }, config: {}, hasRepliedRef: undefined, }); expect(context).toMatchObject({ currentChannelId: "channel:123456789012345678", currentMessageId: "msg-9", }); }); });