{"version":3,"sources":["../src/agentChatClient.ts"],"sourcesContent":["// A {@link ModelClient} (the `agentLoop.ts` provider seam) backed by the host\n// `llm.chat` slot — the SDK {@link chat} stream. PORTED from agent-demo\n// `src/lib/chatModelClient.ts` (GROVE_AGENT_SPEC §7: the loop seam, shared).\n//\n// The provider AND the model are the USER's host-side preference — this client names\n// neither. The app holds no secret/`net:fetch` grant; it needs only the `llm:chat`\n// capability (elevated, app-scoped: a fork earns it by ordinary consent).\nimport { chat } from './llm';\nimport type { ChatRequest, ChatDelta, ContentPart } from './llm';\nimport type { AgentMessage, ModelClient, ReasoningBlock, TextBlock, TokenUsage, ToolUseBlock } from './agentLoop';\nimport type { AgentTool } from './agentLoop';\n\n// The loop's Anthropic-shaped conversation → the chat slot's tool-aware ChatRequest.\n// The agentic history round-trips: a `tool_use` block becomes a `tool-use` content\n// part, a `tool_result` becomes a `tool-result` part.\nfunction toChatMessages(system: string | undefined, messages: AgentMessage[]): ChatRequest['messages'] {\n  const out: ChatRequest['messages'] = [];\n  if (system) out.push({ role: 'system', content: [{ type: 'text', text: system }] });\n  for (const m of messages) {\n    const content: ContentPart[] = m.content.map((b): ContentPart => {\n      if (b.type === 'text') return { type: 'text', text: b.text };\n      if (b.type === 'tool_use') return { type: 'tool-use', id: b.id, name: b.name, input: b.input };\n      // An image the model should LOOK at (the transport already accepts it when the\n      // provider advertises `features.vision`).\n      if (b.type === 'image') return { type: 'image', mimeType: b.mimeType, data: b.data };\n      // Echo the model's own reasoning back. Some providers REQUIRE it (with its\n      // signature) for the next turn of a tool-use chain to be accepted; the host\n      // adapter puts it in the position that provider wants.\n      if (b.type === 'reasoning') {\n        return b.redactedData !== undefined\n          ? { type: 'reasoning-redacted', data: b.redactedData }\n          : { type: 'reasoning', text: b.text, ...(b.signature ? { signature: b.signature } : {}) };\n      }\n      return {\n        type: 'tool-result',\n        toolCallId: b.tool_use_id,\n        content: b.content,\n        ...(b.is_error ? { isError: true } : {}),\n      };\n    });\n    out.push({ role: m.role, content });\n  }\n  return out;\n}\n\nfunction toChatTools(tools: AgentTool[]): NonNullable<ChatRequest['tools']> {\n  return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.input_schema }));\n}\n\n// ChatResult.stopReason ('end'|'length'|'tool'|'filtered') → the loop's\n// Anthropic-style stop reason (the loop branches on 'tool_use' to keep iterating).\nconst mapStop = (s: string): string => (s === 'tool' ? 'tool_use' : s === 'length' ? 'max_tokens' : 'end_turn');\n\n/** A {@link ModelClient} over the host `llm.chat` slot. Streams text deltas (forwarded\n * to `onTextDelta`) and assembles tool calls; the resolved provider + model are the\n * user's preference, never named here. Requires the `llm:chat` capability. */\nexport function createChatModelClient(): ModelClient {\n  return {\n    async createMessage(req) {\n      const chatReq: ChatRequest = {\n        messages: toChatMessages(req.system, req.messages),\n        ...(req.tools.length ? { tools: toChatTools(req.tools) } : {}),\n        modelHint: 'smart',\n        // Hand the loop's abort signal to the host so a stop aborts the in-flight\n        // upstream request (host stops generating + billing), not just the app-side\n        // iterator. `chat()` peels it off before the wire params.\n        ...(req.signal ? { signal: req.signal } : {}),\n      };\n      let text = '';\n      const toolUses: ToolUseBlock[] = [];\n      // Reasoning blocks for this turn, in the order the provider emitted them.\n      const reasoning: ReasoningBlock[] = [];\n      let stopReason = 'end_turn';\n      // The provider `usage` delta — the loop reads it to drive compaction/spend\n      // rather than discarding it. Absent when the provider reports none; the loop\n      // then falls back to a char/4 estimate.\n      let usage: TokenUsage | undefined;\n      const gen = chat(chatReq);\n      for (;;) {\n        const step = await gen.next();\n        if (step.done) {\n          stopReason = mapStop(step.value.stopReason);\n          break;\n        }\n        const d: ChatDelta = step.value;\n        if (d.type === 'text-delta') {\n          text += d.text;\n          req.onTextDelta?.(d.text);\n        } else if (d.type === 'tool-call') {\n          toolUses.push({\n            type: 'tool_use',\n            id: d.id,\n            name: d.name,\n            input: (d.input ?? {}) as Record<string, unknown>,\n          });\n        } else if (d.type === 'reasoning-delta') {\n          // Live only — the WHOLE block arrives as its own delta, carrying the\n          // signature. Accumulating the slices here instead would silently lose it.\n          req.onReasoningDelta?.(d.text);\n        } else if (d.type === 'reasoning') {\n          reasoning.push({ type: 'reasoning', text: d.text, ...(d.signature ? { signature: d.signature } : {}) });\n        } else if (d.type === 'reasoning-redacted') {\n          reasoning.push({ type: 'reasoning', text: '', redactedData: d.data });\n        } else if (d.type === 'usage') {\n          // Carry the cache counters through verbatim, INCLUDING their absence: a\n          // provider that reports nothing must not look like one that cached nothing.\n          usage = {\n            inputTokens: d.inputTokens,\n            outputTokens: d.outputTokens,\n            ...(d.cacheReadTokens !== undefined ? { cacheReadTokens: d.cacheReadTokens } : {}),\n            ...(d.cacheWriteTokens !== undefined ? { cacheWriteTokens: d.cacheWriteTokens } : {}),\n          };\n        }\n      }\n      // Reasoning comes FIRST in the turn: it is what the model did before answering,\n      // and it is the order a provider that requires the echo expects to read back.\n      const content: (TextBlock | ToolUseBlock | ReasoningBlock)[] = [...reasoning];\n      if (text) content.push({ type: 'text', text });\n      content.push(...toolUses);\n      return { content, stopReason, ...(usage ? { usage } : {}) };\n    },\n  };\n}\n"],"mappings":"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","names":[]}