{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @license Apache-2.0\n// SPDX-License-Identifier: Apache-2.0\n\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { buildAIActorEvidence } from \"@loop-engine/actors\";\nimport {\n  actorId,\n  signalId,\n  type ActorAdapter,\n  type AIAgentActor,\n  type AIAgentSubmission,\n  type LoopActorPromptContext\n} from \"@loop-engine/core\";\n\ninterface ParsedModelOutput {\n  signalId: string;\n  reasoning: string;\n  confidence: number;\n  dataPoints?: Record<string, unknown>;\n}\n\n/**\n * Construction-time options for `createAnthropicActorAdapter` per PB-EX-02\n * Option A: provider-specific tuning (`maxTokens`, `temperature`) lives\n * here — not on per-call submission params — so that\n * `ActorAdapter.createSubmission(context: LoopActorPromptContext)` stays\n * narrow and contract-shaped.\n */\nexport interface AnthropicActorAdapterOptions {\n  apiKey: string;\n  model?: string;\n  baseURL?: string;\n  anthropicVersion?: string;\n  maxTokens?: number;\n  temperature?: number;\n  client?: Anthropic;\n}\n\nfunction requireApiKey(apiKey: string, envVar: string): void {\n  if (typeof apiKey !== \"string\" || apiKey.trim().length === 0) {\n    throw new Error(\n      `[loop-engine/adapter-anthropic] Missing API key. This is an external provider-backed adapter and sends prompt/evidence context to Anthropic. Set ${envVar} before creating the adapter.`\n    );\n  }\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n  if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n    return undefined;\n  }\n  return value as Record<string, unknown>;\n}\n\nfunction buildSystemPrompt(): string {\n  return [\n    \"You are an AI actor operating within a governed workflow loop.\",\n    \"Respond with a valid JSON object only. No markdown, no preamble, no text outside the JSON.\",\n    'Your response must be: { \"signalId\": string, \"reasoning\": string, \"confidence\": number, \"dataPoints\"?: object }',\n    \"`signalId` must be one of the signals listed in the user message's availableSignals array.\"\n  ].join(\"\\n\");\n}\n\nfunction buildUserPrompt(context: LoopActorPromptContext): string {\n  return [\n    `Current state: ${context.currentState}`,\n    `Available signals: ${JSON.stringify(context.availableSignals, null, 2)}`,\n    `Evidence: ${JSON.stringify(context.evidence ?? {}, null, 2)}`,\n    `Instruction: ${context.instruction}`\n  ].join(\"\\n\");\n}\n\nfunction parseModelOutput(rawContent: string, context: LoopActorPromptContext): ParsedModelOutput {\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(rawContent);\n  } catch {\n    throw new Error(\"[loop-engine/adapter-anthropic] Invalid JSON response from Anthropic\");\n  }\n\n  const parsedRecord = asRecord(parsed);\n  if (!parsedRecord) {\n    throw new Error(\"[loop-engine/adapter-anthropic] Anthropic response must be a JSON object\");\n  }\n\n  const parsedSignalId = parsedRecord.signalId;\n  const validSignals = context.availableSignals.map((entry) => entry.signalId);\n  if (typeof parsedSignalId !== \"string\" || !validSignals.includes(parsedSignalId)) {\n    throw new Error(\n      \"[loop-engine/adapter-anthropic] Model returned a signalId outside availableSignals\"\n    );\n  }\n\n  const reasoning = parsedRecord.reasoning;\n  if (typeof reasoning !== \"string\" || reasoning.trim().length === 0) {\n    throw new Error(\"[loop-engine/adapter-anthropic] Missing required string field: reasoning\");\n  }\n\n  const confidence = parsedRecord.confidence;\n  if (typeof confidence !== \"number\" || Number.isNaN(confidence)) {\n    throw new Error(\"[loop-engine/adapter-anthropic] Missing required numeric field: confidence\");\n  }\n\n  if (confidence < 0 || confidence > 1) {\n    throw new Error(\"[loop-engine/adapter-anthropic] confidence must be between 0 and 1\");\n  }\n\n  const dataPoints = asRecord(parsedRecord.dataPoints);\n\n  return {\n    signalId: parsedSignalId,\n    reasoning,\n    confidence,\n    ...(dataPoints ? { dataPoints } : {})\n  };\n}\n\nexport function createAnthropicActorAdapter(\n  options: AnthropicActorAdapterOptions\n): ActorAdapter {\n  requireApiKey(options.apiKey, \"ANTHROPIC_API_KEY\");\n  const model = options.model ?? \"claude-3-5-sonnet-latest\";\n  const maxTokens = options.maxTokens ?? 500;\n  const temperature = options.temperature ?? 0;\n  const client =\n    options.client ??\n    new Anthropic({\n      apiKey: options.apiKey,\n      baseURL: options.baseURL,\n      defaultHeaders: options.anthropicVersion\n        ? { \"anthropic-version\": options.anthropicVersion }\n        : undefined\n    });\n\n  return {\n    provider: \"anthropic\",\n    model,\n    async createSubmission(context: LoopActorPromptContext): Promise<AIAgentSubmission> {\n      const systemPrompt = buildSystemPrompt();\n      const userPrompt = buildUserPrompt(context);\n      const fullPrompt = `${systemPrompt}\\n${userPrompt}`;\n\n      let response: Awaited<ReturnType<Anthropic[\"messages\"][\"create\"]>>;\n      try {\n        response = await client.messages.create({\n          model,\n          max_tokens: maxTokens,\n          temperature,\n          system: systemPrompt,\n          messages: [{ role: \"user\", content: userPrompt }]\n        });\n      } catch (error) {\n        const message = error instanceof Error ? error.message : \"Unknown Anthropic error\";\n        throw new Error(`[loop-engine/adapter-anthropic] Anthropic API error: ${message}`);\n      }\n\n      const textBlock = response.content.find((block) => block.type === \"text\");\n      if (!textBlock || textBlock.type !== \"text\") {\n        throw new Error(\"[loop-engine/adapter-anthropic] Anthropic returned no text block\");\n      }\n\n      const parsed = parseModelOutput(textBlock.text, context);\n      const evidenceWithModel = await buildAIActorEvidence({\n        modelId: model,\n        provider: \"anthropic\",\n        reasoning: parsed.reasoning,\n        confidence: parsed.confidence,\n        ...(parsed.dataPoints ? { dataPoints: parsed.dataPoints } : {}),\n        rawResponse: response,\n        prompt: fullPrompt\n      });\n\n      const actor: AIAgentActor = {\n        id: actorId(crypto.randomUUID()),\n        type: \"ai-agent\",\n        modelId: model,\n        provider: \"anthropic\",\n        confidence: evidenceWithModel.confidence,\n        ...(evidenceWithModel.promptHash ? { promptHash: evidenceWithModel.promptHash } : {})\n      };\n\n      const evidence: AIAgentSubmission[\"evidence\"] = {\n        reasoning: evidenceWithModel.reasoning,\n        confidence: evidenceWithModel.confidence,\n        ...(evidenceWithModel.dataPoints ? { dataPoints: evidenceWithModel.dataPoints } : {}),\n        ...(evidenceWithModel.modelResponse !== undefined\n          ? { modelResponse: evidenceWithModel.modelResponse }\n          : {})\n      };\n\n      return {\n        actor,\n        signal: signalId(parsed.signalId),\n        evidence\n      };\n    }\n  };\n}\n"],"mappings":"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","names":["Anthropic"]}