import { getMaxListeners } from 'node:events'; import { type ToolCall } from '#human/llm/message'; import { emptyUsage } from '#human/llm/usage'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { IDisposable } from '#/_base/di/lifecycle'; import { Event } from '#/_base/event'; import { IAgentProfileService } from '#/index'; import { IAgentLLMRequesterService } from '#/agent/llmRequester/llmRequester'; import type { ModelRequestTiming } from '#/llm-adapter/model/model-requester'; import { APIProviderRateLimitError } from '#/llm-adapter/contract/errors'; import type { ContextMessage, PromptOrigin } from '#/agent/contextMemory/types'; import type { LoopRecordedEvent } from '#/agent/contextMemory/loopEventFold'; import { IAgentGoalService } from '#/features/goal/goalService'; import { IAgentLoopService, type Turn } from '#/agent/loop/loop'; import { createActor } from '#human/xstate2'; import { createAgentMachine } from '#human/agent/machine'; import { agentContextOf } from '#/agent/scopeContext/scopeContext'; import type { AgentContext } from '#/agent/agentContext/agentContext'; import { IAgentLifecycleService, type AgentScopeCreatedEvent, } from '#/session/agentLifecycle/agentLifecycle'; import { AssistantDelta, ThinkingDelta, TurnStarted, TurnStepInterrupted, TurnStepStarted, } from '#/agent/loop/turnEvents'; import { TurnEnded } from '#/agent/loop/turnOps'; import type { ExecutableTool } from '#/tool/toolContract'; import { IAgentToolRegistryService } from '#/agent/toolRegistry/toolRegistry'; import { IAgentToolExecutorService } from '#/agent/toolExecutor/toolExecutor'; import { IEventBus } from '#/app/event/eventBus'; import { ITelemetryService } from '#/app/telemetry/telemetry'; import { isUserCancellation, userCancellationReason } from '#/_base/utils/abort'; import { agentService, createTestAgent, InMemoryWireRecordPersistence, permissionModeServices, requesterFromGenerateFn, sessionService, wireRecordPersistenceServices, type TestAgentContext, type TestAgentOptions, } from '../../harness'; import { recordingTelemetry, type TelemetryRecord } from '../../app/telemetry/stubs'; import { submitPromptTurn } from './stubs'; type GenerateFn = NonNullable; describe('Agent loop', () => { let ctx: TestAgentContext; let loop: IAgentLoopService; let profile: IAgentProfileService; beforeEach(async () => { ctx = createTestAgent(); await ctx.restorePersisted(); loop = ctx.get(IAgentLoopService); profile = ctx.get(IAgentProfileService); }); afterEach(async () => { try { await ctx.expectResumeMatches(); } finally { await ctx.dispose(); } }); it('runs a text-only agent turn from prompt to completion', async () => { profile.update({ activeToolNames: [] }); ctx.mockNextResponse( { type: 'think', think: '' }, { type: 'text', text: '' }, { type: 'think', think: '' }, { type: 'text', text: '' }, ); await ctx.rpc.prompt({ input: [{ type: 'text', text: 'Hello' }] }); expect(await ctx.untilTurnEnd()).toMatchInlineSnapshot(` [wire] tools.set_active_tools { "agentId": "main", "names": [], "time": "