@johpaz/hive-sdk 0.1.4 → 0.1.6
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 +129 -0
- package/README.md +78 -23
- package/bun.lock +55 -29
- 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 +17 -12
- 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 +13 -12
- package/packages/core/src/agent/acceptance-checks.ts +172 -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 +76 -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-backend.ts +129 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
- package/packages/core/src/tools/web/browser-service.ts +80 -35
- 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/tools/web/webview-backend.ts +412 -0
- 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/acceptance-checks.test.ts +403 -0
- package/test/agent-loop-terminal-synthesis.test.ts +32 -0
- package/test/browser-backend.test.ts +308 -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/tool-selector-runtime-tools.test.ts +117 -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
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Voice Tools - 2 tools
|
|
3
|
-
*
|
|
4
|
-
* @category voice
|
|
5
|
-
*/
|
|
6
|
-
import type { Tool } from "../types.ts";
|
|
7
|
-
import { logger } from "../../utils/logger.ts";
|
|
8
|
-
|
|
9
|
-
const log = logger.child("voice");
|
|
10
|
-
|
|
11
|
-
// ─── voice_transcribe ────────────────────────────────────────────────────────
|
|
12
|
-
|
|
13
|
-
export const voiceTranscribeTool: Tool = {
|
|
14
|
-
name: "voice_transcribe",
|
|
15
|
-
description: "Transcribe audio input to text. Spanish: transcribir audio, voz a texto, reconocimiento de voz",
|
|
16
|
-
parameters: {
|
|
17
|
-
type: "object",
|
|
18
|
-
properties: {
|
|
19
|
-
audio: {
|
|
20
|
-
type: "string",
|
|
21
|
-
description: "Audio file path or base64 encoded audio",
|
|
22
|
-
},
|
|
23
|
-
language: {
|
|
24
|
-
type: "string",
|
|
25
|
-
description: "Language code (e.g., 'es', 'en'). Auto-detect if not specified.",
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
required: ["audio"],
|
|
29
|
-
},
|
|
30
|
-
execute: async (params: Record<string, unknown>) => {
|
|
31
|
-
const audio = params.audio as string;
|
|
32
|
-
const language = (params.language as string) ?? "auto";
|
|
33
|
-
|
|
34
|
-
log.info(`Transcribing audio: ${audio.substring(0, 50)}...`);
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
// Placeholder implementation - real transcription would call STT API
|
|
38
|
-
return {
|
|
39
|
-
ok: true,
|
|
40
|
-
transcription: "[Transcription requires STT provider configuration]",
|
|
41
|
-
language: language,
|
|
42
|
-
duration: 0,
|
|
43
|
-
confidence: 0,
|
|
44
|
-
};
|
|
45
|
-
} catch (error) {
|
|
46
|
-
return {
|
|
47
|
-
ok: false,
|
|
48
|
-
error: `Failed to transcribe: ${(error as Error).message}`,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// ─── voice_speak ─────────────────────────────────────────────────────────────
|
|
55
|
-
|
|
56
|
-
export const voiceSpeakTool: Tool = {
|
|
57
|
-
name: "voice_speak",
|
|
58
|
-
description: "Convert text to synthesized speech output. Spanish: texto a voz, sintetizar, hablar, leer en voz alta",
|
|
59
|
-
parameters: {
|
|
60
|
-
type: "object",
|
|
61
|
-
properties: {
|
|
62
|
-
text: {
|
|
63
|
-
type: "string",
|
|
64
|
-
description: "Text to convert to speech",
|
|
65
|
-
},
|
|
66
|
-
voice_id: {
|
|
67
|
-
type: "string",
|
|
68
|
-
description: "Voice ID to use (provider-specific)",
|
|
69
|
-
},
|
|
70
|
-
language: {
|
|
71
|
-
type: "string",
|
|
72
|
-
description: "Language code (e.g., 'es', 'en')",
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
required: ["text"],
|
|
76
|
-
},
|
|
77
|
-
execute: async (params: Record<string, unknown>) => {
|
|
78
|
-
const text = params.text as string;
|
|
79
|
-
const voiceId = (params.voice_id as string) ?? "default";
|
|
80
|
-
const language = (params.language as string) ?? "es";
|
|
81
|
-
|
|
82
|
-
log.info(`Synthesizing speech: ${text.substring(0, 50)}...`);
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
// Placeholder implementation - real TTS would call TTS API
|
|
86
|
-
return {
|
|
87
|
-
ok: true,
|
|
88
|
-
audio_url: "[TTS requires provider configuration]",
|
|
89
|
-
voice_id: voiceId,
|
|
90
|
-
language: language,
|
|
91
|
-
duration: 0,
|
|
92
|
-
};
|
|
93
|
-
} catch (error) {
|
|
94
|
-
return {
|
|
95
|
-
ok: false,
|
|
96
|
-
error: `Failed to synthesize speech: ${(error as Error).message}`,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export function createTools(): Tool[] {
|
|
103
|
-
return [voiceTranscribeTool, voiceSpeakTool];
|
|
104
|
-
}
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
-
import { apiRequestTool } from "./api-request.ts";
|
|
3
|
-
|
|
4
|
-
describe("apiRequestTool", () => {
|
|
5
|
-
let originalFetch: typeof fetch;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
originalFetch = globalThis.fetch;
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
globalThis.fetch = originalFetch;
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
function mockFetch(response: Response) {
|
|
16
|
-
globalThis.fetch = Object.assign(async () => response, { preconnect: async () => undefined }) as typeof fetch;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function mockResponse(
|
|
20
|
-
body: BodyInit,
|
|
21
|
-
init: ResponseInit & { headers?: Record<string, string> } = {}
|
|
22
|
-
): Response {
|
|
23
|
-
return new Response(body, {
|
|
24
|
-
status: init.status ?? 200,
|
|
25
|
-
statusText: init.statusText ?? "OK",
|
|
26
|
-
headers: init.headers ?? {},
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
it("executes a simple GET request", async () => {
|
|
31
|
-
let capturedUrl = "";
|
|
32
|
-
let capturedInit: RequestInit = {};
|
|
33
|
-
globalThis.fetch = Object.assign(async (url, init) => {
|
|
34
|
-
capturedUrl = url.toString();
|
|
35
|
-
capturedInit = init ?? {};
|
|
36
|
-
return mockResponse(JSON.stringify({ ok: true }), { headers: { "content-type": "application/json" } });
|
|
37
|
-
}, { preconnect: async () => undefined }) as typeof fetch;
|
|
38
|
-
|
|
39
|
-
const result = await apiRequestTool.execute({ url: "https://api.example.com/data" });
|
|
40
|
-
|
|
41
|
-
expect(capturedUrl).toBe("https://api.example.com/data");
|
|
42
|
-
expect(capturedInit.method).toBe("GET");
|
|
43
|
-
expect(result).toMatchObject({
|
|
44
|
-
ok: true,
|
|
45
|
-
status: 200,
|
|
46
|
-
data: { ok: true },
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("sends POST with JSON body", async () => {
|
|
51
|
-
let capturedInit: RequestInit = {};
|
|
52
|
-
globalThis.fetch = Object.assign(async (_url, init) => {
|
|
53
|
-
capturedInit = init ?? {};
|
|
54
|
-
return mockResponse(JSON.stringify({ id: 1 }), { headers: { "content-type": "application/json" } });
|
|
55
|
-
}, { preconnect: async () => undefined }) as typeof fetch;
|
|
56
|
-
|
|
57
|
-
const result = await apiRequestTool.execute({
|
|
58
|
-
url: "https://api.example.com/items",
|
|
59
|
-
method: "POST",
|
|
60
|
-
body: { name: "test" },
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
expect(capturedInit.method).toBe("POST");
|
|
64
|
-
expect(capturedInit.headers).toMatchObject({ "content-type": "application/json" });
|
|
65
|
-
expect(capturedInit.body).toBe(JSON.stringify({ name: "test" }));
|
|
66
|
-
expect(result).toMatchObject({ ok: true, data: { id: 1 } });
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("applies bearer auth", async () => {
|
|
70
|
-
let capturedInit: RequestInit = {};
|
|
71
|
-
globalThis.fetch = Object.assign(async (_url, init) => {
|
|
72
|
-
capturedInit = init ?? {};
|
|
73
|
-
return mockResponse("{}");
|
|
74
|
-
}, { preconnect: async () => undefined }) as typeof fetch;
|
|
75
|
-
|
|
76
|
-
await apiRequestTool.execute({
|
|
77
|
-
url: "https://api.example.com/private",
|
|
78
|
-
auth: { type: "bearer", token: "secret-token" },
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
expect(new Headers(capturedInit.headers).get("Authorization")).toBe("Bearer secret-token");
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("applies basic auth", async () => {
|
|
85
|
-
let capturedInit: RequestInit = {};
|
|
86
|
-
globalThis.fetch = Object.assign(async (_url, init) => {
|
|
87
|
-
capturedInit = init ?? {};
|
|
88
|
-
return mockResponse("{}");
|
|
89
|
-
}, { preconnect: async () => undefined }) as typeof fetch;
|
|
90
|
-
|
|
91
|
-
await apiRequestTool.execute({
|
|
92
|
-
url: "https://api.example.com/private",
|
|
93
|
-
auth: { type: "basic", username: "alice", password: "secret" },
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
expect(new Headers(capturedInit.headers).get("Authorization")).toBe(`Basic ${btoa("alice:secret")}`);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("applies api_key in header", async () => {
|
|
100
|
-
let capturedInit: RequestInit = {};
|
|
101
|
-
globalThis.fetch = Object.assign(async (_url, init) => {
|
|
102
|
-
capturedInit = init ?? {};
|
|
103
|
-
return mockResponse("{}");
|
|
104
|
-
}, { preconnect: async () => undefined }) as typeof fetch;
|
|
105
|
-
|
|
106
|
-
await apiRequestTool.execute({
|
|
107
|
-
url: "https://api.example.com/private",
|
|
108
|
-
auth: { type: "api_key", in: "header", name: "X-API-Key", value: "abc123" },
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
expect(new Headers(capturedInit.headers).get("X-API-Key")).toBe("abc123");
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("applies api_key in query", async () => {
|
|
115
|
-
let capturedUrl = "";
|
|
116
|
-
globalThis.fetch = Object.assign(async (url, _init) => {
|
|
117
|
-
capturedUrl = url.toString();
|
|
118
|
-
return mockResponse("{}");
|
|
119
|
-
}, { preconnect: async () => undefined }) as typeof fetch;
|
|
120
|
-
|
|
121
|
-
await apiRequestTool.execute({
|
|
122
|
-
url: "https://api.example.com/private",
|
|
123
|
-
auth: { type: "api_key", in: "query", name: "api_key", value: "abc123" },
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
expect(capturedUrl).toBe("https://api.example.com/private?api_key=abc123");
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("returns error for non-ok HTTP response", async () => {
|
|
130
|
-
globalThis.fetch = Object.assign(async () => mockResponse("Not found", { status: 404, statusText: "Not Found" }), { preconnect: async () => undefined }) as typeof fetch;
|
|
131
|
-
|
|
132
|
-
const result = await apiRequestTool.execute({ url: "https://api.example.com/missing" });
|
|
133
|
-
|
|
134
|
-
expect(result).toMatchObject({
|
|
135
|
-
ok: false,
|
|
136
|
-
status: 404,
|
|
137
|
-
error: "HTTP 404: Not Found",
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("rejects non-http URLs", async () => {
|
|
142
|
-
const result = await apiRequestTool.execute({ url: "file:///etc/passwd" });
|
|
143
|
-
|
|
144
|
-
expect(result).toMatchObject({
|
|
145
|
-
ok: false,
|
|
146
|
-
error: "Invalid URL. Only http:// and https:// are allowed.",
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("respects timeout", async () => {
|
|
151
|
-
globalThis.fetch = Object.assign(async (_url, init) => {
|
|
152
|
-
expect(init?.signal).toBeDefined();
|
|
153
|
-
return mockResponse("{}");
|
|
154
|
-
}, { preconnect: async () => undefined }) as typeof fetch;
|
|
155
|
-
|
|
156
|
-
const result = await apiRequestTool.execute({ url: "https://api.example.com/data", timeoutMs: 5000 });
|
|
157
|
-
expect(result).toMatchObject({ ok: true });
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("returns text for non-json responses", async () => {
|
|
161
|
-
globalThis.fetch = Object.assign(async () => mockResponse("hello world", { headers: { "content-type": "text/plain" } }), { preconnect: async () => undefined }) as typeof fetch;
|
|
162
|
-
|
|
163
|
-
const result = await apiRequestTool.execute({ url: "https://api.example.com/text" });
|
|
164
|
-
|
|
165
|
-
expect(result).toMatchObject({
|
|
166
|
-
ok: true,
|
|
167
|
-
data: "hello world",
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
});
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* api_request - Make generic HTTP requests to connect REST APIs
|
|
3
|
-
*
|
|
4
|
-
* @category web
|
|
5
|
-
* @seedId api_request
|
|
6
|
-
* @spanish conectar api, peticion http, llamada api, rest api, endpoint
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { Tool } from "../types.ts";
|
|
10
|
-
import { logger } from "../../utils/logger.ts";
|
|
11
|
-
|
|
12
|
-
const log = logger.child("api-request");
|
|
13
|
-
|
|
14
|
-
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
15
|
-
const MAX_RESPONSE_CHARS = 100_000;
|
|
16
|
-
|
|
17
|
-
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
|
|
18
|
-
export type ResponseFormat = "auto" | "json" | "text" | "binary";
|
|
19
|
-
|
|
20
|
-
export interface ApiAuthBearer {
|
|
21
|
-
type: "bearer";
|
|
22
|
-
token: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface ApiAuthBasic {
|
|
26
|
-
type: "basic";
|
|
27
|
-
username: string;
|
|
28
|
-
password: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ApiAuthApiKey {
|
|
32
|
-
type: "api_key";
|
|
33
|
-
in: "header" | "query";
|
|
34
|
-
name: string;
|
|
35
|
-
value: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type ApiAuth = ApiAuthBearer | ApiAuthBasic | ApiAuthApiKey;
|
|
39
|
-
|
|
40
|
-
function applyAuth(
|
|
41
|
-
url: string,
|
|
42
|
-
init: RequestInit,
|
|
43
|
-
auth?: ApiAuth
|
|
44
|
-
): { url: string; init: RequestInit } {
|
|
45
|
-
if (!auth) return { url, init };
|
|
46
|
-
|
|
47
|
-
const headers = new Headers(init.headers);
|
|
48
|
-
|
|
49
|
-
switch (auth.type) {
|
|
50
|
-
case "bearer":
|
|
51
|
-
headers.set("Authorization", `Bearer ${auth.token}`);
|
|
52
|
-
break;
|
|
53
|
-
case "basic": {
|
|
54
|
-
const credentials = btoa(`${auth.username}:${auth.password}`);
|
|
55
|
-
headers.set("Authorization", `Basic ${credentials}`);
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
case "api_key":
|
|
59
|
-
if (auth.in === "query") {
|
|
60
|
-
const parsed = new URL(url);
|
|
61
|
-
parsed.searchParams.set(auth.name, auth.value);
|
|
62
|
-
url = parsed.toString();
|
|
63
|
-
} else {
|
|
64
|
-
headers.set(auth.name, auth.value);
|
|
65
|
-
}
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return { url, init: { ...init, headers } };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function isValidHttpUrl(url: string): boolean {
|
|
73
|
-
try {
|
|
74
|
-
const parsed = new URL(url);
|
|
75
|
-
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function parseResponse(
|
|
82
|
-
response: Response,
|
|
83
|
-
format: ResponseFormat
|
|
84
|
-
): Promise<{ data: unknown; contentType: string }> {
|
|
85
|
-
const contentType = response.headers.get("content-type") || "";
|
|
86
|
-
|
|
87
|
-
if (format === "json" || (format === "auto" && contentType.includes("application/json"))) {
|
|
88
|
-
const json = await response.json();
|
|
89
|
-
return { data: json, contentType };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (format === "text" || (format === "auto" && contentType.includes("text/"))) {
|
|
93
|
-
const text = await response.text();
|
|
94
|
-
return { data: text.slice(0, MAX_RESPONSE_CHARS), contentType };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (format === "binary") {
|
|
98
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
99
|
-
const base64 = Buffer.from(arrayBuffer).toString("base64");
|
|
100
|
-
return { data: base64, contentType };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Fallback: try text, then JSON
|
|
104
|
-
const text = await response.text();
|
|
105
|
-
if (text.trim().startsWith("{") || text.trim().startsWith("[")) {
|
|
106
|
-
try {
|
|
107
|
-
return { data: JSON.parse(text), contentType };
|
|
108
|
-
} catch {
|
|
109
|
-
/* fallthrough */
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return { data: text.slice(0, MAX_RESPONSE_CHARS), contentType };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export const apiRequestTool: Tool = {
|
|
116
|
-
name: "api_request",
|
|
117
|
-
description:
|
|
118
|
-
"Connect to REST APIs: make HTTP requests with methods, headers, body and authentication. Spanish: conectar api, peticion http, llamada api, rest api, endpoint, bearer, api key",
|
|
119
|
-
parameters: {
|
|
120
|
-
type: "object",
|
|
121
|
-
properties: {
|
|
122
|
-
url: {
|
|
123
|
-
type: "string",
|
|
124
|
-
description: "The API endpoint URL (http:// or https://)",
|
|
125
|
-
},
|
|
126
|
-
method: {
|
|
127
|
-
type: "string",
|
|
128
|
-
enum: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
|
|
129
|
-
description: "HTTP method (default: GET)",
|
|
130
|
-
},
|
|
131
|
-
headers: {
|
|
132
|
-
type: "object",
|
|
133
|
-
additionalProperties: { type: "string" },
|
|
134
|
-
description: "Optional HTTP headers as key-value pairs",
|
|
135
|
-
},
|
|
136
|
-
body: {
|
|
137
|
-
type: "string",
|
|
138
|
-
description: "Request body. Objects should be passed as JSON strings; strings are sent as-is",
|
|
139
|
-
},
|
|
140
|
-
auth: {
|
|
141
|
-
type: "object",
|
|
142
|
-
description: "Optional authentication configuration",
|
|
143
|
-
properties: {
|
|
144
|
-
type: {
|
|
145
|
-
type: "string",
|
|
146
|
-
enum: ["bearer", "basic", "api_key"],
|
|
147
|
-
},
|
|
148
|
-
token: { type: "string" },
|
|
149
|
-
username: { type: "string" },
|
|
150
|
-
password: { type: "string" },
|
|
151
|
-
in: { type: "string", enum: ["header", "query"] },
|
|
152
|
-
name: { type: "string" },
|
|
153
|
-
value: { type: "string" },
|
|
154
|
-
},
|
|
155
|
-
required: ["type"],
|
|
156
|
-
},
|
|
157
|
-
timeoutMs: {
|
|
158
|
-
type: "number",
|
|
159
|
-
description: "Request timeout in milliseconds (default: 30000)",
|
|
160
|
-
},
|
|
161
|
-
responseFormat: {
|
|
162
|
-
type: "string",
|
|
163
|
-
enum: ["auto", "json", "text", "binary"],
|
|
164
|
-
description: "How to parse the response (default: auto)",
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
required: ["url"],
|
|
168
|
-
},
|
|
169
|
-
execute: async (params: Record<string, unknown>) => {
|
|
170
|
-
const url = params.url as string;
|
|
171
|
-
const method = (params.method as HttpMethod) ?? "GET";
|
|
172
|
-
const headers = (params.headers as Record<string, string>) ?? {};
|
|
173
|
-
const bodyParam = params.body;
|
|
174
|
-
const auth = params.auth as ApiAuth | undefined;
|
|
175
|
-
const timeoutMs = (params.timeoutMs as number) ?? DEFAULT_TIMEOUT_MS;
|
|
176
|
-
const responseFormat = (params.responseFormat as ResponseFormat) ?? "auto";
|
|
177
|
-
|
|
178
|
-
if (!isValidHttpUrl(url)) {
|
|
179
|
-
return { ok: false, error: "Invalid URL. Only http:// and https:// are allowed." };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
log.info(`API request: ${method} ${url}`);
|
|
183
|
-
|
|
184
|
-
let body: BodyInit | undefined;
|
|
185
|
-
const finalHeaders: Record<string, string> = { ...headers };
|
|
186
|
-
|
|
187
|
-
if (bodyParam !== undefined) {
|
|
188
|
-
if (typeof bodyParam === "string") {
|
|
189
|
-
body = bodyParam;
|
|
190
|
-
} else {
|
|
191
|
-
body = JSON.stringify(bodyParam);
|
|
192
|
-
if (!finalHeaders["content-type"] && !finalHeaders["Content-Type"]) {
|
|
193
|
-
finalHeaders["content-type"] = "application/json";
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
let init: RequestInit = {
|
|
199
|
-
method,
|
|
200
|
-
headers: finalHeaders,
|
|
201
|
-
body,
|
|
202
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const final = applyAuth(url, init, auth);
|
|
206
|
-
|
|
207
|
-
try {
|
|
208
|
-
const response = await fetch(final.url, final.init);
|
|
209
|
-
const { data, contentType } = await parseResponse(response, responseFormat);
|
|
210
|
-
|
|
211
|
-
const responseHeaders: Record<string, string> = {};
|
|
212
|
-
response.headers.forEach((value, key) => {
|
|
213
|
-
responseHeaders[key] = value;
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
const result = {
|
|
217
|
-
ok: response.ok,
|
|
218
|
-
status: response.status,
|
|
219
|
-
statusText: response.statusText,
|
|
220
|
-
url: final.url,
|
|
221
|
-
contentType,
|
|
222
|
-
headers: responseHeaders,
|
|
223
|
-
data,
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
if (!response.ok) {
|
|
227
|
-
log.warn(`API request returned ${response.status} for ${final.url}`);
|
|
228
|
-
return { ok: false, error: `HTTP ${response.status}: ${response.statusText}`, ...result };
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
log.info(`API request successful: ${response.status} ${final.url}`);
|
|
232
|
-
return result;
|
|
233
|
-
} catch (error) {
|
|
234
|
-
const message = (error as Error).message;
|
|
235
|
-
log.error(`API request failed: ${message}`);
|
|
236
|
-
return { ok: false, error: `API request failed: ${message}` };
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
};
|