"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateAiAgentService = generateAiAgentService;
const Generator = __importStar(require("@postxl/generator"));
function generateAiAgentService({ context }) {
    const imports = Generator.ImportGenerator.from(Generator.toFilePath('./ai-agent.service.ts')).addImport({
        from: context.types.location,
        items: [Generator.toTypeName('AskStepInput'), Generator.toTypeName('AskTaskInput')],
    });
    return /* ts */ `
import { Inject, Injectable } from '@nestjs/common'

${imports.generate()}

import { AiCacheService } from './ai-cache.service'
import { AiConfig } from './ai-config'
import type {
  AiModelProvider,
  ChatMessage,
} from './model-provider/model-provider.interface'
import { AiToolsService, BackendToolExecutionLog } from './ai-tools.service'

export const AI_MODEL_PROVIDER = Symbol('AI_MODEL_PROVIDER')

type ResolvedToolCall = {
  messages: ChatMessage[]
  key: string
  input: Record<string, unknown>
  pendingToolCallId?: string
  backendLogs: BackendToolExecutionLog[]
}

@Injectable()
export class AiAgentService {
  private readonly systemPrompt = [
    'You are an AI assistant controlling tools to satisfy a user task.',
    'Choose one tool call at a time and stop once task is completed.',
    'Call done when task is complete or no further progress can be made.',
    'Do not repeat the same tool twice in a row.',
    'If required information is missing or ambiguous, call ask-user with a clear question and wait for the answer.',
  ].join('\\n')

  constructor(
    private readonly cache: AiCacheService,
    private readonly aiConfig: AiConfig,
    private readonly tools: AiToolsService,
    @Inject(AI_MODEL_PROVIDER) private readonly modelProvider: AiModelProvider,
  ) {}

  public async startExecution({ task, tools }: AskTaskInput, actorId: unknown) {
    const actorKey = this.toActorKey(actorId)
    if (!this.aiConfig.config.enabled) {
      throw new Error('AI feature is disabled')
    }

    this.cache.pruneConversationCache()

    const messages: ChatMessage[] = [
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: task },
    ]

    const resolved = await this.resolveNextToolCall({ messages, tools, actorKey })

    const conversation = resolved.key !== 'done' && resolved.pendingToolCallId
      ? this.cache.createConversation({
          actorKey,
          pendingToolCallId: resolved.pendingToolCallId,
          messages: resolved.messages,
        })
      : (globalThis.crypto.randomUUID() as string)

    return {
      conversation,
      key: resolved.key,
      input: resolved.input,
      backendLogs: resolved.backendLogs,
    }
  }

  public async continueExecution({ conversation, output, tools }: AskStepInput, actorId: unknown) {
    const actorKey = this.toActorKey(actorId)
    const history = this.cache.getConversation(conversation, actorKey)

    if (!history?.pendingToolCallId) {
      throw new Error('Conversation not found')
    }

    const toolOutput = output ? JSON.stringify(output) : JSON.stringify({ ok: true })

    const messages: ChatMessage[] = [
      ...history.messages,
      {
        role: 'tool',
        tool_call_id: history.pendingToolCallId,
        content: toolOutput,
      },
    ]

    const resolved = await this.resolveNextToolCall({ messages, tools, actorKey })

    if (resolved.key === 'done') {
      this.cache.deleteConversation(conversation, actorKey)
    } else if (resolved.pendingToolCallId) {
      this.cache.upsertConversation(conversation, {
        actorKey,
        pendingToolCallId: resolved.pendingToolCallId,
        messages: resolved.messages,
      })
    }

    return {
      conversation,
      key: resolved.key,
      input: resolved.input,
      backendLogs: resolved.backendLogs,
    }
  }

  public cancelExecution(conversation: string, actorId: unknown) {
    const actorKey = this.toActorKey(actorId)
    return this.cache.deleteConversation(conversation, actorKey)
  }

  private async resolveNextToolCall({
    messages,
    tools,
    actorKey,
  }: {
    messages: ChatMessage[]
    tools: AskTaskInput['tools']
    actorKey: string
  }): Promise<ResolvedToolCall> {
    const normalizedFrontendTools = this.tools.normalizeFrontendTools(tools)
    this.tools.assertNoToolKeyCollision(normalizedFrontendTools)

    const conversationMessages = [...messages]
    const backendLogs: BackendToolExecutionLog[] = []

    for (let step = 0; step < this.aiConfig.config.maxInternalToolSteps; step += 1) {
      this.cache.assertWithinRateLimit(actorKey)

      const providerResult = await this.modelProvider.requestSingleToolCall({
        messages: conversationMessages,
        tools: this.tools.buildTools(normalizedFrontendTools),
      })

      conversationMessages.push(providerResult.assistantMessage)

      const parsedInput = this.parseArguments(providerResult.toolCall.function.arguments)

      if (providerResult.toolCall.function.name === 'done') {
        return {
          messages: conversationMessages,
          key: 'done',
          input: parsedInput,
          backendLogs,
        }
      }

      const backendTool = this.tools.getBackendTool(providerResult.toolCall.function.name)
      if (!backendTool) {
        return {
          messages: conversationMessages,
          key: providerResult.toolCall.function.name,
          input: parsedInput,
          pendingToolCallId: providerResult.toolCall.id,
          backendLogs,
        }
      }

      const toolLabel = backendTool.definition.label

      try {
        const output = (await backendTool.execute(parsedInput, { actorKey })) ?? { ok: true }
        backendLogs.push({
          toolKey: backendTool.definition.key,
          status: 'done',
          text: 'Executed backend tool: ' + toolLabel,
        })

        conversationMessages.push({
          role: 'tool',
          tool_call_id: providerResult.toolCall.id,
          content: JSON.stringify(output),
        })
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error)
        backendLogs.push({
          toolKey: backendTool.definition.key,
          status: 'error',
          text: 'Backend tool failed: ' + toolLabel + ' (' + message + ')',
        })

        conversationMessages.push({
          role: 'tool',
          tool_call_id: providerResult.toolCall.id,
          content: JSON.stringify({ ok: false, error: message }),
        })
      }
    }

    throw new Error('AI exceeded backend tool iteration limit')
  }

  private parseArguments(argumentsJson: string): Record<string, unknown> {
    try {
      const parsed = JSON.parse(argumentsJson)
      if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
        return parsed as Record<string, unknown>
      }

      console.warn('AI action arguments must be a JSON object. Received:', parsed)
      return {}
    } catch (error) {
      console.warn('Failed to parse AI action arguments:', error)
      return {}
    }
  }

  private toActorKey(userId: unknown): string {
    if (typeof userId === 'string' && userId.length > 0) {
      return userId
    }

    return String(userId ?? 'unknown-user')
  }
}
`;
}
//# sourceMappingURL=ai-agent-service.generator.js.map