@johpaz/hive-sdk 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/README.md +78 -23
- package/bunfig.toml +4 -2
- package/docs/API-AGENTS.md +78 -27
- package/docs/API-CONTEXT-COMPILER.md +31 -34
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
- package/docs/HIVE-HARNESS.md +1 -1
- package/docs/INDEX.md +4 -4
- package/docs/TEMPLATE-HIVE-APP.md +10 -10
- package/package.json +9 -4
- package/packages/cli/package.json +2 -2
- package/packages/cli/src/commands/create-app.test.ts +36 -7
- package/packages/cli/src/commands/init.ts +3 -3
- package/packages/cli/src/commands/run.ts +1 -1
- package/packages/cli/src/commands/test.ts +37 -25
- package/packages/cli/src/commands/trace.ts +30 -28
- package/packages/cli/templates/hive-app/.env.example +10 -2
- package/packages/cli/templates/hive-app/README.md +103 -0
- package/packages/cli/templates/hive-app/hive.config.ts +9 -3
- package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
- package/packages/cli/templates/hive-app/src/main.ts +12 -19
- package/packages/core/package.json +5 -4
- package/packages/core/src/agent/acceptance-checks.ts +166 -0
- package/packages/core/src/agent/agent-catalog.ts +348 -0
- package/packages/core/src/agent/agent-loop.ts +1373 -0
- package/packages/core/src/agent/capability-search.ts +186 -0
- package/packages/core/src/agent/catalog-selector.ts +103 -0
- package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
- package/packages/core/src/agent/context-compiler.ts +689 -0
- package/packages/core/src/agent/conversation-store.ts +381 -0
- package/packages/core/src/agent/curator.ts +276 -0
- package/packages/core/src/agent/delegation-runtime.ts +241 -0
- package/packages/core/src/agent/goal-runner.ts +323 -0
- package/packages/core/src/agent/index.ts +17 -12
- package/packages/core/src/agent/llm-client.ts +266 -0
- package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
- package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
- package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
- package/packages/core/src/agent/llm-providers/groq.ts +5 -0
- package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
- package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
- package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
- package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
- package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
- package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
- package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
- package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
- package/packages/core/src/agent/llm-providers/openai.ts +5 -0
- package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
- package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
- package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
- package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
- package/packages/core/src/agent/minimal-loadout.ts +47 -0
- package/packages/core/src/agent/playbook-selector.ts +119 -0
- package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
- package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
- package/packages/core/src/agent/providers/index.ts +35 -16
- package/packages/core/src/agent/reflector.ts +320 -0
- package/packages/core/src/agent/routing-intent.ts +22 -0
- package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
- package/packages/core/src/{harness → agent}/run-store.ts +142 -81
- package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
- package/packages/core/src/agent/skill-selector.ts +374 -0
- package/packages/core/src/agent/stuck-loop.ts +209 -0
- package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
- package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
- package/packages/core/src/api/createAgent.test.ts +139 -27
- package/packages/core/src/api/createAgent.ts +232 -44
- package/packages/core/src/artifacts/store.ts +162 -0
- package/packages/core/src/canvas/canvas-manager.ts +161 -0
- package/packages/core/src/canvas/canvas.test.ts +8 -4
- package/packages/core/src/canvas/emitter.ts +131 -80
- package/packages/core/src/canvas/index.ts +1 -3
- package/packages/core/src/channels/base.ts +9 -1
- package/packages/core/src/channels/discord.ts +5 -4
- package/packages/core/src/channels/manager.ts +122 -30
- package/packages/core/src/channels/slack.ts +5 -4
- package/packages/core/src/channels/telegram.ts +36 -6
- package/packages/core/src/channels/webchat.ts +11 -10
- package/packages/core/src/channels/whatsapp.ts +23 -7
- package/packages/core/src/config/index.ts +13 -2
- package/packages/core/src/config/loader.ts +71 -29
- package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
- package/packages/core/src/ethics/EthicsGuard.ts +51 -47
- package/packages/core/src/events/agent-bus.ts +44 -68
- package/packages/core/src/events/channel-narration.ts +150 -0
- package/packages/core/src/events/narration.ts +82 -0
- package/packages/core/src/events/tool-narration.ts +62 -0
- package/packages/core/src/gateway/delegation-groups.ts +258 -0
- package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
- package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
- package/packages/core/src/gateway/lane-queue.ts +173 -0
- package/packages/core/src/gateway/notification-inbox.ts +57 -0
- package/packages/core/src/gateway/server.ts +1 -1
- package/packages/core/src/harness/index.ts +46 -27
- package/packages/core/src/index.ts +33 -27
- package/packages/core/src/mcp/hot-reload.ts +32 -23
- package/packages/core/src/mcp/index.ts +6 -3
- package/packages/core/src/mcp/singleton.ts +1 -4
- package/packages/core/src/mcp/tool-sync.ts +138 -0
- package/packages/core/src/memory/Scratchpad.test.ts +39 -20
- package/packages/core/src/memory/Scratchpad.ts +27 -34
- package/packages/core/src/multimodal/vision-service.ts +44 -38
- package/packages/core/src/resilience/retry.ts +95 -0
- package/packages/core/src/scheduler/CronScheduler.ts +334 -287
- package/packages/core/src/scheduler/index.ts +9 -7
- package/packages/core/src/scheduler/integration.ts +46 -26
- package/packages/core/src/scheduler/scheduler.test.ts +9 -13
- package/packages/core/src/scheduler/types.ts +7 -2
- package/packages/core/src/security/Pairing.ts +1 -1
- package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
- package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
- package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
- package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
- package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
- package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
- package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
- package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
- package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
- package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
- package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
- package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
- package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
- package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
- package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
- package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
- package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
- package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
- package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
- package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
- package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
- package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
- package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
- package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
- package/packages/core/src/skills/skills.test.ts +52 -11
- package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
- package/packages/core/src/storage/bootstrap.ts +151 -0
- package/packages/core/src/storage/causal-events.ts +84 -0
- package/packages/core/src/storage/collections.ts +680 -0
- package/packages/core/src/storage/crypto.ts +205 -74
- package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
- package/packages/core/src/storage/hivedb.ts +61 -0
- package/packages/core/src/storage/index.ts +111 -18
- package/packages/core/src/storage/model-id.ts +53 -0
- package/packages/core/src/storage/onboarding.ts +540 -972
- package/packages/core/src/storage/reconcile.ts +238 -0
- package/packages/core/src/storage/seed.ts +572 -406
- package/packages/core/src/storage/usage.ts +285 -225
- package/packages/core/src/storage/user-email.ts +11 -0
- package/packages/core/src/swarm/AgentExecutor.ts +1 -1
- package/packages/core/src/swarm/EventBridge.ts +1 -1
- package/packages/core/src/swarm/index.ts +12 -9
- package/packages/core/src/tool-runtime/index.ts +146 -23
- package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
- package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
- package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
- package/packages/core/src/tools/agents/get-available-models.ts +36 -54
- package/packages/core/src/tools/agents/index.ts +784 -292
- package/packages/core/src/tools/api/api-request.test.ts +164 -0
- package/packages/core/src/tools/api/api-request.ts +174 -0
- package/packages/core/src/tools/api/index.ts +16 -0
- package/packages/core/src/tools/cli/index.ts +4 -0
- package/packages/core/src/tools/core/index.ts +281 -112
- package/packages/core/src/tools/cron/index.ts +121 -124
- package/packages/core/src/tools/index.ts +63 -78
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
- package/packages/core/src/tools/types.ts +3 -1
- package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
- package/packages/core/src/tools/web/browser-service.ts +5 -0
- package/packages/core/src/tools/web/browser-type.ts +3 -8
- package/packages/core/src/tools/web/index.ts +4 -4
- package/packages/core/src/voice/index.ts +89 -63
- package/packages/core/src/workers/agent.worker.ts +2 -2
- package/packages/core/src/workers/workers.test.ts +3 -10
- package/scripts/bump-version.ts +248 -0
- package/scripts/generate-skill-bundle.ts +108 -0
- package/test/agent-loop-terminal-synthesis.test.ts +32 -0
- package/test/catalog-agents-stay-enabled.test.ts +117 -0
- package/test/causal-events.test.ts +117 -0
- package/test/compaction.test.ts +105 -0
- package/test/context-compiler.test.ts +269 -0
- package/test/curator.test.ts +130 -0
- package/test/durable-queue.test.ts +114 -0
- package/test/harness-barrel.test.ts +64 -0
- package/test/hive-helpers.test.ts +130 -0
- package/test/hivedb-search.test.ts +189 -0
- package/test/internal-turns.test.ts +166 -0
- package/test/job-idempotency.test.ts +68 -0
- package/test/job-retry-backoff.test.ts +184 -0
- package/test/job-store.test.ts +381 -0
- package/test/llm-retry.test.ts +97 -0
- package/test/memory-perf.test.ts +774 -0
- package/test/minimal-loadout.test.ts +78 -0
- package/test/model-catalog.test.ts +105 -0
- package/test/preload.ts +12 -0
- package/test/reflector.test.ts +320 -0
- package/test/retention-cap.test.ts +91 -0
- package/test/retired-capabilities-pruned.test.ts +192 -0
- package/test/run-store.test.ts +355 -0
- package/test/scratchpad.test.ts +74 -0
- package/test/secrets-durability.test.ts +119 -0
- package/test/seed-model-reseed.test.ts +155 -0
- package/test/setup-agent-seed.test.ts +264 -0
- package/test/tool-inventory.test.ts +65 -0
- package/test/tool-runtime.test.ts +258 -0
- package/test/toon.test.ts +429 -0
- package/tsconfig.json +2 -0
- package/packages/core/src/ace/Curator.ts +0 -158
- package/packages/core/src/ace/Reflector.ts +0 -200
- package/packages/core/src/ace/index.ts +0 -4
- package/packages/core/src/agent/AgentRunner.ts +0 -711
- package/packages/core/src/agent/ContextCompiler.ts +0 -567
- package/packages/core/src/agent/ContextGuard.ts +0 -91
- package/packages/core/src/agent/ConversationStore.ts +0 -254
- package/packages/core/src/agent/Hooks.ts +0 -166
- package/packages/core/src/agent/StuckLoop.ts +0 -133
- package/packages/core/src/agent/providers/LLMClient.ts +0 -149
- package/packages/core/src/agent/providers/anthropic.ts +0 -212
- package/packages/core/src/agent/providers/openai-compat.ts +0 -231
- package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
- package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
- package/packages/core/src/agent/selectors/index.ts +0 -6
- package/packages/core/src/auth/auth.ts +0 -121
- package/packages/core/src/auth/index.ts +0 -1
- package/packages/core/src/canvas/CanvasManager.ts +0 -390
- package/packages/core/src/canvas/canvas-tools.ts +0 -448
- package/packages/core/src/harness/collections.ts +0 -98
- package/packages/core/src/harness/goal-verifier.ts +0 -141
- package/packages/core/src/harness/harness.test.ts +0 -236
- package/packages/core/src/harness/reconcile.ts +0 -149
- package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
- package/packages/core/src/multimodal/VisionService.ts +0 -293
- package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
- package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
- package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
- package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
- package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
- package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
- package/packages/core/src/scheduler/dag/errors.ts +0 -37
- package/packages/core/src/scheduler/dag/index.ts +0 -26
- package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
- package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
- package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
- package/packages/core/src/storage/HiveDBStorage.ts +0 -64
- package/packages/core/src/storage/SQLiteStorage.ts +0 -414
- package/packages/core/src/storage/hiveSeed.ts +0 -308
- package/packages/core/src/storage/hiveStorage.test.ts +0 -38
- package/packages/core/src/storage/schema.ts +0 -689
- package/packages/core/src/storage/storage.test.ts +0 -37
- package/packages/core/src/swarm/AgentBus.ts +0 -460
- package/packages/core/src/swarm/EventBus.ts +0 -169
- package/packages/core/src/swarm/WorkerPool.ts +0 -236
- package/packages/core/src/tools/bridge-events.ts +0 -26
- package/packages/core/src/tools/canvas/index.ts +0 -375
- package/packages/core/src/tools/codebridge/index.ts +0 -342
- package/packages/core/src/tools/meeting/index.ts +0 -353
- package/packages/core/src/tools/projects/index.ts +0 -37
- package/packages/core/src/tools/projects/project-create.ts +0 -94
- package/packages/core/src/tools/projects/project-done.ts +0 -66
- package/packages/core/src/tools/projects/project-fail.ts +0 -66
- package/packages/core/src/tools/projects/project-list.ts +0 -96
- package/packages/core/src/tools/projects/project-update.ts +0 -72
- package/packages/core/src/tools/projects/task-create.ts +0 -68
- package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
- package/packages/core/src/tools/projects/task-update.ts +0 -93
- package/packages/core/src/tools/voice/index.ts +0 -104
- package/packages/core/src/tools/web/api-request.test.ts +0 -170
- package/packages/core/src/tools/web/api-request.ts +0 -239
- package/test/setup-db.ts +0 -216
- /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM client — direct official SDKs, no abstraction layers.
|
|
3
|
+
*
|
|
4
|
+
* gemini / google → native Gemini REST API (v1beta, ?key=)
|
|
5
|
+
* anthropic → @anthropic-ai/sdk
|
|
6
|
+
* ollama → ollama npm package
|
|
7
|
+
* openai → openai npm package
|
|
8
|
+
* groq / mistral / openrouter / deepseek / kimi / nvidia / qwen
|
|
9
|
+
* → openai npm package (OpenAI-compatible endpoint, per-provider adapter)
|
|
10
|
+
*
|
|
11
|
+
* Public interface (LLMMessage, callLLM, resolveProviderConfig) is stable.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { logger } from "../utils/logger"
|
|
15
|
+
import { loadConfig } from "../config/loader"
|
|
16
|
+
import { withRetry, isRetryableError, type RetryPolicy } from "../resilience/retry"
|
|
17
|
+
import { GeminiProvider } from "./llm-providers/gemini"
|
|
18
|
+
import { AnthropicProvider } from "./llm-providers/anthropic"
|
|
19
|
+
import { OllamaProvider } from "./llm-providers/ollama"
|
|
20
|
+
import { OpenAIProvider } from "./llm-providers/openai"
|
|
21
|
+
import { GroqProvider } from "./llm-providers/groq"
|
|
22
|
+
import { MistralProvider } from "./llm-providers/mistral"
|
|
23
|
+
import { OpenRouterProvider } from "./llm-providers/openrouter"
|
|
24
|
+
import { DeepSeekProvider } from "./llm-providers/deepseek"
|
|
25
|
+
import { KimiProvider } from "./llm-providers/kimi"
|
|
26
|
+
import { NvidiaProvider } from "./llm-providers/nvidia"
|
|
27
|
+
import { QwenProvider } from "./llm-providers/qwen"
|
|
28
|
+
import { MiniMaxProvider } from "./llm-providers/minimax"
|
|
29
|
+
import { OpenCodeGoProvider } from "./llm-providers/opencode-go"
|
|
30
|
+
import { HiveAgentsProvider } from "./llm-providers/hiveagents"
|
|
31
|
+
import { ZaiProvider } from "./llm-providers/z-ai"
|
|
32
|
+
import { ModelScopeProvider } from "./llm-providers/modelscope"
|
|
33
|
+
import type { LLMProvider } from "./llm-providers/interface"
|
|
34
|
+
|
|
35
|
+
const log = logger.child("llm-client")
|
|
36
|
+
|
|
37
|
+
// ─── Canonical types ───────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
export interface LLMToolCall {
|
|
40
|
+
id: string
|
|
41
|
+
type: "function"
|
|
42
|
+
function: { name: string; arguments: string }
|
|
43
|
+
/** Gemini 3.x thought signature — must be round-tripped for tool-calling. */
|
|
44
|
+
thought_signature?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type ContentPart =
|
|
48
|
+
| { type: "text"; text: string }
|
|
49
|
+
| { type: "image_url"; image_url: { url: string } }
|
|
50
|
+
| { type: "image_base64"; base64: string; mimeType: string }
|
|
51
|
+
| { type: "document"; base64: string; mimeType: string; fileName?: string }
|
|
52
|
+
|
|
53
|
+
/** Raw Anthropic extended-thinking content block, round-tripped verbatim (signature required by the API when tool_use follows). */
|
|
54
|
+
export interface ThinkingBlock {
|
|
55
|
+
type: "thinking" | "redacted_thinking"
|
|
56
|
+
thinking?: string
|
|
57
|
+
signature?: string
|
|
58
|
+
data?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface LLMMessage {
|
|
62
|
+
role: "system" | "user" | "assistant" | "tool"
|
|
63
|
+
content: string | ContentPart[]
|
|
64
|
+
tool_calls?: LLMToolCall[]
|
|
65
|
+
tool_call_id?: string
|
|
66
|
+
name?: string
|
|
67
|
+
/** Kimi K2 thinking mode — must be round-tripped when tool calls are present. */
|
|
68
|
+
reasoning_content?: string
|
|
69
|
+
/** Anthropic extended thinking — must be round-tripped verbatim when tool calls are present. */
|
|
70
|
+
thinking_blocks?: ThinkingBlock[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface LLMToolDef {
|
|
74
|
+
type: "function"
|
|
75
|
+
function: {
|
|
76
|
+
name: string
|
|
77
|
+
description: string
|
|
78
|
+
parameters: Record<string, unknown>
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface LLMCallOptions {
|
|
83
|
+
provider: string
|
|
84
|
+
model: string
|
|
85
|
+
apiKey: string
|
|
86
|
+
baseUrl?: string
|
|
87
|
+
numCtx?: number
|
|
88
|
+
contextWindow?: number
|
|
89
|
+
messages: LLMMessage[]
|
|
90
|
+
tools?: LLMToolDef[]
|
|
91
|
+
temperature?: number
|
|
92
|
+
maxTokens?: number
|
|
93
|
+
numGpu?: number
|
|
94
|
+
onToken?: (token: string) => void
|
|
95
|
+
/** Live reasoning/thinking tokens as they stream, for display only (never sent back to the LLM). */
|
|
96
|
+
onReasoningToken?: (token: string) => void
|
|
97
|
+
signal?: AbortSignal
|
|
98
|
+
/** Enable extended thinking for supported models (Anthropic Claude 3.7+). */
|
|
99
|
+
thinking?: { enabled: boolean; budget_tokens?: number }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface LLMResponse {
|
|
103
|
+
content: string
|
|
104
|
+
tool_calls?: LLMToolCall[]
|
|
105
|
+
stop_reason: "stop" | "tool_calls" | "max_tokens" | "error"
|
|
106
|
+
usage?: { input_tokens: number; output_tokens: number; thinking_tokens?: number }
|
|
107
|
+
/** Kimi K2 / DeepSeek thinking mode — must be round-tripped in assistant messages. */
|
|
108
|
+
reasoning_content?: string
|
|
109
|
+
/** Anthropic extended thinking content (not sent to LLM, for display only). */
|
|
110
|
+
thinking_content?: string
|
|
111
|
+
/** Anthropic extended thinking raw blocks — must be round-tripped verbatim when tool calls follow. */
|
|
112
|
+
thinking_blocks?: ThinkingBlock[]
|
|
113
|
+
/**
|
|
114
|
+
* Set only when stop_reason === "error". `content` carries a human-readable
|
|
115
|
+
* version of the same failure for display, but it is NOT model output —
|
|
116
|
+
* callers must check this field before persisting `content` anywhere durable
|
|
117
|
+
* (conversation history, summaries, task results).
|
|
118
|
+
*/
|
|
119
|
+
error?: {
|
|
120
|
+
message: string
|
|
121
|
+
status?: number
|
|
122
|
+
/** The model id no longer resolves at the provider (HTTP 404/410) — the config needs to change, retrying won't help. */
|
|
123
|
+
modelUnavailable?: boolean
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─── Provider factory ─────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
function getProvider(provider: string): LLMProvider {
|
|
130
|
+
switch (provider) {
|
|
131
|
+
case "gemini":
|
|
132
|
+
case "google": return new GeminiProvider()
|
|
133
|
+
case "anthropic": return new AnthropicProvider()
|
|
134
|
+
case "ollama": return new OllamaProvider()
|
|
135
|
+
case "openai": return new OpenAIProvider()
|
|
136
|
+
case "groq": return new GroqProvider()
|
|
137
|
+
case "mistral": return new MistralProvider()
|
|
138
|
+
case "openrouter": return new OpenRouterProvider()
|
|
139
|
+
case "deepseek": return new DeepSeekProvider()
|
|
140
|
+
case "kimi": return new KimiProvider()
|
|
141
|
+
case "nvidia": return new NvidiaProvider()
|
|
142
|
+
case "qwen": return new QwenProvider()
|
|
143
|
+
case "minimax": return new MiniMaxProvider()
|
|
144
|
+
case "opencode-go": return new OpenCodeGoProvider()
|
|
145
|
+
case "hiveagents": return new HiveAgentsProvider()
|
|
146
|
+
case "z-ai": return new ZaiProvider()
|
|
147
|
+
case "modelscope": return new ModelScopeProvider()
|
|
148
|
+
default:
|
|
149
|
+
log.warn(`[llm-client] Unknown provider "${provider}" — falling back to OpenAI-compatible endpoint`)
|
|
150
|
+
return new OpenAIProvider()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── Public API ────────────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Call any LLM provider. Returns a canonical LLMResponse regardless of provider.
|
|
158
|
+
*
|
|
159
|
+
* Retries transient failures (429/5xx/timeout/network) with exponential
|
|
160
|
+
* backoff + jitter per `config.retry`, honoring `Retry-After` when the
|
|
161
|
+
* provider sends one. An aborted signal never retries. Note: this retries
|
|
162
|
+
* the whole call, so a failure that happens mid-stream (after onToken has
|
|
163
|
+
* already fired) can produce a duplicated partial response — acceptable
|
|
164
|
+
* because provider failures overwhelmingly happen before the stream starts
|
|
165
|
+
* (auth/rate-limit/connection errors).
|
|
166
|
+
*/
|
|
167
|
+
export async function callLLM(options: LLMCallOptions): Promise<LLMResponse> {
|
|
168
|
+
const retryCfg = loadConfig().retry
|
|
169
|
+
const policy: RetryPolicy = {
|
|
170
|
+
maxAttempts: retryCfg?.maxAttempts ?? 3,
|
|
171
|
+
initialDelayMs: retryCfg?.initialDelayMs ?? 1000,
|
|
172
|
+
backoffMultiplier: retryCfg?.backoffMultiplier ?? 2,
|
|
173
|
+
maxDelayMs: retryCfg?.maxDelayMs ?? 30000,
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
return await withRetry(
|
|
177
|
+
() => getProvider(options.provider).call(options),
|
|
178
|
+
policy,
|
|
179
|
+
(err) => !options.signal?.aborted && isRetryableError(err)
|
|
180
|
+
)
|
|
181
|
+
} catch (err) {
|
|
182
|
+
const cleanModel = options.model.replace(new RegExp(`^${options.provider}\\/`), "")
|
|
183
|
+
const status = extractErrorStatus(err)
|
|
184
|
+
const modelUnavailable = status === 404 || status === 410
|
|
185
|
+
const msg = modelUnavailable
|
|
186
|
+
? `El modelo "${cleanModel}" ya no existe en ${options.provider} (HTTP ${status}). `
|
|
187
|
+
+ `El proveedor lo retiró de su catálogo; reintentar no sirve. `
|
|
188
|
+
+ `Elegí otro modelo en Ajustes → Proveedores.`
|
|
189
|
+
: (err as Error).message
|
|
190
|
+
log.error(`[llm-client] Error calling ${options.provider}/${cleanModel}: ${msg}`, err)
|
|
191
|
+
return {
|
|
192
|
+
content: `[LLM Error] ${msg}`,
|
|
193
|
+
stop_reason: "error",
|
|
194
|
+
error: { message: msg, status, modelUnavailable },
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Provider SDKs disagree on where the HTTP status lands; check every shape we've seen. */
|
|
200
|
+
function extractErrorStatus(err: unknown): number | undefined {
|
|
201
|
+
const e = err as { status?: number; statusCode?: number; response?: { status?: number } }
|
|
202
|
+
return e?.status ?? e?.statusCode ?? e?.response?.status
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Default provider/model resolved from the DB, used when an agent has none configured:
|
|
207
|
+
* 1. the coordinator agent's config, 2. the first active LLM model of an active provider.
|
|
208
|
+
* Returns null when the DB has no usable LLM (e.g. fresh install before setup).
|
|
209
|
+
*/
|
|
210
|
+
export async function getDefaultLLM(): Promise<{ provider: string; model: string } | null> {
|
|
211
|
+
const { col, fromIndexable } = await import("../storage/hive")
|
|
212
|
+
const agentsCol = await col<import("../storage/collections").AgentDoc>("agents")
|
|
213
|
+
const modelsCol = await col<import("../storage/collections").ModelDoc>("models")
|
|
214
|
+
const providersCol = await col<import("../storage/collections").ProviderDoc>("providers")
|
|
215
|
+
|
|
216
|
+
const coordinators = await agentsCol.findBy("role", "coordinator")
|
|
217
|
+
const coordinator = coordinators[0]
|
|
218
|
+
const coordinatorProvider = fromIndexable(coordinator?.doc.provider_id ?? null)
|
|
219
|
+
const coordinatorModel = fromIndexable(coordinator?.doc.model_id ?? null)
|
|
220
|
+
if (coordinatorProvider && coordinatorModel) {
|
|
221
|
+
return { provider: coordinatorProvider, model: coordinatorModel }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const activeModels = (await modelsCol.findBy("model_type", "llm")).filter(m => m.doc.active)
|
|
225
|
+
for (const m of activeModels) {
|
|
226
|
+
const provider = await providersCol.get(m.doc.provider_id)
|
|
227
|
+
if (provider?.doc.active) {
|
|
228
|
+
return { provider: m.doc.provider_id, model: m.doc.id }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return null
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Resolve provider config from DB (decrypts API key).
|
|
236
|
+
*/
|
|
237
|
+
export async function resolveProviderConfig(
|
|
238
|
+
providerId: string,
|
|
239
|
+
modelId: string
|
|
240
|
+
): Promise<Pick<LLMCallOptions, "provider" | "model" | "apiKey" | "baseUrl" | "numCtx" | "numGpu" | "contextWindow">> {
|
|
241
|
+
const { col } = await import("../storage/hive")
|
|
242
|
+
const { loadProviderApiKey } = await import("../storage/crypto")
|
|
243
|
+
const providersCol = await col<import("../storage/collections").ProviderDoc>("providers")
|
|
244
|
+
const modelsCol = await col<import("../storage/collections").ModelDoc>("models")
|
|
245
|
+
|
|
246
|
+
const providerEntry = await providersCol.get(providerId)
|
|
247
|
+
const providerRow = (providerEntry?.doc.enabled && providerEntry?.doc.active) ? providerEntry.doc : undefined
|
|
248
|
+
|
|
249
|
+
// Load model's context window for token budget management
|
|
250
|
+
const modelEntry = await modelsCol.get(modelId)
|
|
251
|
+
|
|
252
|
+
let apiKey = await loadProviderApiKey(providerId)
|
|
253
|
+
if (!apiKey) {
|
|
254
|
+
apiKey = process.env[`${providerId.toUpperCase()}_API_KEY`] || ""
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
provider: providerId,
|
|
259
|
+
model: modelId,
|
|
260
|
+
apiKey,
|
|
261
|
+
baseUrl: providerRow?.base_url || undefined,
|
|
262
|
+
numCtx: providerRow?.num_ctx ?? undefined,
|
|
263
|
+
numGpu: providerRow?.num_gpu ?? undefined,
|
|
264
|
+
contextWindow: modelEntry?.doc.context_window ?? undefined,
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { logger } from "../../utils/logger"
|
|
2
|
+
import { normalizeToolName, resolveMaxTokens, ensureArrayItems } from "./interface"
|
|
3
|
+
import type { LLMCallOptions, LLMProvider, LLMResponse, LLMToolCall, ThinkingBlock } from "./interface"
|
|
4
|
+
import type { ContentPart, LLMMessage } from "../llm-client"
|
|
5
|
+
|
|
6
|
+
const log = logger.child("llm-client")
|
|
7
|
+
|
|
8
|
+
// Models that accept the explicit `thinking: { type: "enabled" }` parameter, per
|
|
9
|
+
// the "Extended thinking" row of platform.claude.com/docs/en/about-claude/models/overview.
|
|
10
|
+
// This is an allowlist on purpose: from Opus 4.7 onward Anthropic replaced extended
|
|
11
|
+
// thinking with *adaptive* thinking, which is server-side and always on — those models
|
|
12
|
+
// reject the parameter, so being absent here is the correct behavior, not an omission.
|
|
13
|
+
const THINKING_CAPABLE_MODELS = new Set([
|
|
14
|
+
"claude-3-7-sonnet-20250219",
|
|
15
|
+
"claude-sonnet-4-5",
|
|
16
|
+
"claude-sonnet-4-5-20250929",
|
|
17
|
+
"claude-sonnet-4-6", // deprecated but still accepted
|
|
18
|
+
"claude-opus-4-5",
|
|
19
|
+
"claude-opus-4-5-20251101",
|
|
20
|
+
"claude-opus-4-6", // deprecated but still accepted
|
|
21
|
+
"claude-haiku-4-5",
|
|
22
|
+
"claude-haiku-4-5-20251001",
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
function supportsThinking(model: string): boolean {
|
|
26
|
+
return THINKING_CAPABLE_MODELS.has(model)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Anthropic's documented shape for context-length errors: 400 + invalid_request_error + a "too long"/"maximum" message. */
|
|
30
|
+
function isAnthropicContextOverflowError(err: any): boolean {
|
|
31
|
+
const status = err?.status
|
|
32
|
+
if (status !== 400) return false
|
|
33
|
+
const type = err?.error?.type ?? err?.type
|
|
34
|
+
const msg = (err?.error?.message ?? err?.message ?? "").toLowerCase()
|
|
35
|
+
if (type && type !== "invalid_request_error") return false
|
|
36
|
+
return msg.includes("too long") || msg.includes("maximum context") || msg.includes("prompt is too long") || msg.includes("context length")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Keeps the first `thinking`/`tool_use` round-trip block intact per message, keeps only the last ~33% of the conversation, and shrinks max_tokens. */
|
|
40
|
+
function compactAnthropicBody(body: any): void {
|
|
41
|
+
const keepRatio = Math.max(1, Math.floor(body.messages.length / 3))
|
|
42
|
+
body.messages = body.messages.slice(-keepRatio)
|
|
43
|
+
if (body.max_tokens) body.max_tokens = Math.min(body.max_tokens, 4096)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class AnthropicProvider implements LLMProvider {
|
|
47
|
+
private _convertContentPart(part: ContentPart): any {
|
|
48
|
+
switch (part.type) {
|
|
49
|
+
case "text":
|
|
50
|
+
return { type: "text", text: part.text }
|
|
51
|
+
case "image_url": {
|
|
52
|
+
const url = part.image_url.url
|
|
53
|
+
if (url.startsWith("data:")) {
|
|
54
|
+
const match = url.match(/^data:([^;]+);base64,(.+)$/)
|
|
55
|
+
if (match) return { type: "image", source: { type: "base64", media_type: match[1], data: match[2] } }
|
|
56
|
+
}
|
|
57
|
+
return { type: "image", source: { type: "url", url } }
|
|
58
|
+
}
|
|
59
|
+
case "image_base64":
|
|
60
|
+
return { type: "image", source: { type: "base64", media_type: part.mimeType, data: part.base64 } }
|
|
61
|
+
case "document":
|
|
62
|
+
return { type: "document", source: { type: "base64", media_type: part.mimeType, data: part.base64 } }
|
|
63
|
+
default:
|
|
64
|
+
return { type: "text", text: JSON.stringify(part) }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private _convertUserContent(msg: LLMMessage): any[] {
|
|
69
|
+
if (Array.isArray(msg.content)) {
|
|
70
|
+
return msg.content.map(p => this._convertContentPart(p))
|
|
71
|
+
}
|
|
72
|
+
return [{ type: "text", text: msg.content }]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async call(options: LLMCallOptions): Promise<LLMResponse> {
|
|
76
|
+
const Anthropic = await import("@anthropic-ai/sdk")
|
|
77
|
+
const client = new Anthropic.default({ apiKey: options.apiKey })
|
|
78
|
+
|
|
79
|
+
// Anthropic requires tool names to match ^[a-zA-Z0-9_-]{1,128}$
|
|
80
|
+
// Native Hive tools use dots (e.g. cron.create) which violate this.
|
|
81
|
+
const toolNameMap = new Map<string, string>() // wireName -> originalName
|
|
82
|
+
|
|
83
|
+
const systemText = options.messages
|
|
84
|
+
.filter((m) => m.role === "system")
|
|
85
|
+
.map((m) => m.content)
|
|
86
|
+
.join("\n\n")
|
|
87
|
+
|
|
88
|
+
const anthropicMessages: any[] = []
|
|
89
|
+
|
|
90
|
+
for (const msg of options.messages) {
|
|
91
|
+
if (msg.role === "system") continue
|
|
92
|
+
|
|
93
|
+
if (msg.role === "tool") {
|
|
94
|
+
const block = { type: "tool_result", tool_use_id: msg.tool_call_id, content: msg.content }
|
|
95
|
+
const last = anthropicMessages[anthropicMessages.length - 1]
|
|
96
|
+
if (last?.role === "user" && Array.isArray(last.content)) {
|
|
97
|
+
last.content.push(block)
|
|
98
|
+
} else {
|
|
99
|
+
anthropicMessages.push({ role: "user", content: [block] })
|
|
100
|
+
}
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (msg.role === "assistant" && msg.tool_calls?.length) {
|
|
105
|
+
const content: any[] = []
|
|
106
|
+
// Extended thinking blocks must be replayed verbatim (with signature) before
|
|
107
|
+
// any tool_use block, or the API rejects the request with a 400.
|
|
108
|
+
if (msg.thinking_blocks?.length) {
|
|
109
|
+
for (const tb of msg.thinking_blocks) {
|
|
110
|
+
content.push(
|
|
111
|
+
tb.type === "thinking"
|
|
112
|
+
? { type: "thinking", thinking: tb.thinking, signature: tb.signature }
|
|
113
|
+
: { type: "redacted_thinking", data: tb.data }
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (msg.content) content.push({ type: "text", text: msg.content })
|
|
118
|
+
for (const tc of msg.tool_calls) {
|
|
119
|
+
let input: Record<string, unknown>
|
|
120
|
+
try { input = JSON.parse(tc.function.arguments || "{}") } catch { input = {} }
|
|
121
|
+
const wireName = normalizeToolName(tc.function.name, "_")
|
|
122
|
+
if (wireName !== tc.function.name) toolNameMap.set(wireName, tc.function.name)
|
|
123
|
+
content.push({ type: "tool_use", id: tc.id, name: wireName, input })
|
|
124
|
+
}
|
|
125
|
+
anthropicMessages.push({ role: "assistant", content })
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
anthropicMessages.push({ role: msg.role, content: Array.isArray(msg.content) ? this._convertUserContent(msg) : msg.content })
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const tools: any[] = (options.tools ?? []).map((t) => {
|
|
133
|
+
const originalName = t.function.name
|
|
134
|
+
const wireName = normalizeToolName(originalName, "_")
|
|
135
|
+
if (wireName !== originalName) toolNameMap.set(wireName, originalName)
|
|
136
|
+
return {
|
|
137
|
+
name: wireName,
|
|
138
|
+
description: t.function.description,
|
|
139
|
+
input_schema: ensureArrayItems(t.function.parameters),
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const body: any = {
|
|
144
|
+
model: options.model,
|
|
145
|
+
max_tokens: resolveMaxTokens(options.maxTokens, options.contextWindow) ?? 16384,
|
|
146
|
+
messages: anthropicMessages,
|
|
147
|
+
}
|
|
148
|
+
if (systemText) body.system = systemText
|
|
149
|
+
if (tools.length) body.tools = tools
|
|
150
|
+
|
|
151
|
+
// Extended thinking — only for supported models
|
|
152
|
+
const thinkingEnabled = options.thinking?.enabled && supportsThinking(options.model)
|
|
153
|
+
if (thinkingEnabled) {
|
|
154
|
+
body.thinking = { type: "enabled", budget_tokens: options.thinking?.budget_tokens ?? 10000 }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
log.info(
|
|
158
|
+
`[llm-client] anthropic/${options.model} — ${anthropicMessages.length} msgs, ${tools.length} tools` +
|
|
159
|
+
(thinkingEnabled ? ` thinking=${body.thinking.budget_tokens}tok` : "")
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
// Streaming via messages.stream()
|
|
163
|
+
const useStream = true // Always stream for better UX
|
|
164
|
+
if (useStream) {
|
|
165
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
166
|
+
let content = ""
|
|
167
|
+
let thinking_content = ""
|
|
168
|
+
const tool_calls: LLMToolCall[] = []
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const stream = client.messages.stream(body, options.signal ? { signal: options.signal } : undefined)
|
|
172
|
+
|
|
173
|
+
// Track partial tool inputs by index
|
|
174
|
+
const partialInputs: Record<number, string> = {}
|
|
175
|
+
const toolMeta: Record<number, { id: string; name: string }> = {}
|
|
176
|
+
// Track thinking/redacted_thinking blocks by index — needed both for live
|
|
177
|
+
// display (onReasoningToken) and to round-trip the signed block verbatim
|
|
178
|
+
// in the next turn if this response also contains tool calls.
|
|
179
|
+
const thinkingBlockState: Record<number, { type: "thinking" | "redacted_thinking"; thinking: string; signature: string; data: string }> = {}
|
|
180
|
+
|
|
181
|
+
for await (const event of stream) {
|
|
182
|
+
if (event.type === "content_block_start") {
|
|
183
|
+
if (event.content_block.type === "tool_use") {
|
|
184
|
+
const wireName = event.content_block.name
|
|
185
|
+
const originalName = toolNameMap.get(wireName) ?? wireName
|
|
186
|
+
toolMeta[event.index] = { id: event.content_block.id, name: originalName }
|
|
187
|
+
partialInputs[event.index] = ""
|
|
188
|
+
} else if (event.content_block.type === "thinking") {
|
|
189
|
+
thinkingBlockState[event.index] = { type: "thinking", thinking: "", signature: "", data: "" }
|
|
190
|
+
} else if ((event.content_block as any).type === "redacted_thinking") {
|
|
191
|
+
thinkingBlockState[event.index] = { type: "redacted_thinking", thinking: "", signature: "", data: (event.content_block as any).data ?? "" }
|
|
192
|
+
}
|
|
193
|
+
} else if (event.type === "content_block_delta") {
|
|
194
|
+
if (event.delta.type === "text_delta") {
|
|
195
|
+
content += event.delta.text
|
|
196
|
+
if (options.onToken) options.onToken(event.delta.text)
|
|
197
|
+
} else if (event.delta.type === "thinking_delta") {
|
|
198
|
+
thinking_content += event.delta.thinking
|
|
199
|
+
options.onReasoningToken?.(event.delta.thinking)
|
|
200
|
+
if (thinkingBlockState[event.index]) thinkingBlockState[event.index].thinking += event.delta.thinking
|
|
201
|
+
} else if ((event.delta as any).type === "signature_delta") {
|
|
202
|
+
if (thinkingBlockState[event.index]) thinkingBlockState[event.index].signature += (event.delta as any).signature
|
|
203
|
+
} else if (event.delta.type === "input_json_delta") {
|
|
204
|
+
if (partialInputs[event.index] !== undefined) {
|
|
205
|
+
partialInputs[event.index] += event.delta.partial_json
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const thinking_blocks: ThinkingBlock[] = Object.keys(thinkingBlockState)
|
|
212
|
+
.map(Number)
|
|
213
|
+
.sort((a, b) => a - b)
|
|
214
|
+
.map((idx) => {
|
|
215
|
+
const b = thinkingBlockState[idx]
|
|
216
|
+
return b.type === "thinking"
|
|
217
|
+
? { type: "thinking" as const, thinking: b.thinking, signature: b.signature }
|
|
218
|
+
: { type: "redacted_thinking" as const, data: b.data }
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const finalMsg = await stream.finalMessage()
|
|
222
|
+
|
|
223
|
+
// Build tool_calls from accumulated partial inputs
|
|
224
|
+
for (const [idx, meta] of Object.entries(toolMeta)) {
|
|
225
|
+
const args = partialInputs[Number(idx)] ?? "{}"
|
|
226
|
+
tool_calls.push({
|
|
227
|
+
id: meta.id,
|
|
228
|
+
type: "function",
|
|
229
|
+
function: { name: meta.name, arguments: args },
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const usage = finalMsg.usage
|
|
234
|
+
return {
|
|
235
|
+
content,
|
|
236
|
+
thinking_content: thinking_content || undefined,
|
|
237
|
+
thinking_blocks: thinking_blocks.length ? thinking_blocks : undefined,
|
|
238
|
+
tool_calls: tool_calls.length ? tool_calls : undefined,
|
|
239
|
+
stop_reason:
|
|
240
|
+
finalMsg.stop_reason === "tool_use" ? "tool_calls"
|
|
241
|
+
: finalMsg.stop_reason === "max_tokens" ? "max_tokens"
|
|
242
|
+
: "stop",
|
|
243
|
+
usage: {
|
|
244
|
+
input_tokens: usage.input_tokens,
|
|
245
|
+
output_tokens: usage.output_tokens,
|
|
246
|
+
thinking_tokens: (usage as any).thinking_tokens ?? 0,
|
|
247
|
+
},
|
|
248
|
+
}
|
|
249
|
+
} catch (err: any) {
|
|
250
|
+
if (attempt === 0 && isAnthropicContextOverflowError(err)) {
|
|
251
|
+
log.warn(`[llm-client] anthropic: context overflow — compacting messages and retrying`)
|
|
252
|
+
const originalCount = body.messages.length
|
|
253
|
+
compactAnthropicBody(body)
|
|
254
|
+
log.info(`[llm-client] anthropic: compacted ${originalCount} msgs → ${body.messages.length} msgs, max_tokens=${body.max_tokens}`)
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
257
|
+
throw err
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
throw new Error("unreachable")
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { OpenAICompatBase } from "./openai-compat-base"
|
|
2
|
+
|
|
3
|
+
export class DeepSeekProvider extends OpenAICompatBase {
|
|
4
|
+
constructor() { super("deepseek") }
|
|
5
|
+
|
|
6
|
+
/** DeepSeek reasoner models return reasoning_content that must be round-tripped. */
|
|
7
|
+
protected needsReasoningRoundtrip(): boolean { return true }
|
|
8
|
+
}
|