@zhive/cli 0.6.0 → 0.6.1

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.
Files changed (88) hide show
  1. package/dist/{services/agent → agent}/analysis.js +5 -5
  2. package/dist/agent/app.js +122 -0
  3. package/dist/agent/commands/registry.js +12 -0
  4. package/dist/agent/components/AsciiTicker.js +81 -0
  5. package/dist/agent/components/CommandInput.js +65 -0
  6. package/dist/agent/components/HoneycombBoot.js +291 -0
  7. package/dist/agent/components/Spinner.js +37 -0
  8. package/dist/agent/hooks/useAgent.js +480 -0
  9. package/dist/{services/agent/prompts → agent}/memory-prompt.js +22 -20
  10. package/dist/{services/agent/helpers → agent}/model.js +2 -2
  11. package/dist/agent/process-lifecycle.js +18 -0
  12. package/dist/{services/agent/prompts → agent}/prompt.js +54 -80
  13. package/dist/agent/run-headless.js +189 -0
  14. package/dist/agent/theme.js +41 -0
  15. package/dist/{services/agent → agent}/tools/market/client.js +1 -1
  16. package/dist/{services/agent → agent}/tools/mindshare/client.js +1 -1
  17. package/dist/agent/types.js +1 -0
  18. package/dist/{services/config/agent.js → agents.js} +2 -2
  19. package/dist/avatar.js +34 -0
  20. package/dist/backtest/default-backtest-data.js +200 -0
  21. package/dist/backtest/fetch.js +41 -0
  22. package/dist/backtest/import.js +106 -0
  23. package/dist/backtest/index.js +10 -0
  24. package/dist/backtest/results.js +113 -0
  25. package/dist/backtest/runner.js +134 -0
  26. package/dist/backtest/storage.js +11 -0
  27. package/dist/backtest/types.js +1 -0
  28. package/dist/commands/install.js +50 -0
  29. package/dist/commands/start/ui/PollText.js +23 -0
  30. package/dist/commands/start/ui/PredictionsPanel.js +88 -0
  31. package/dist/commands/start/ui/SpinnerContext.js +20 -0
  32. package/dist/components/InputGuard.js +6 -0
  33. package/dist/components/stdout-spinner.js +48 -0
  34. package/dist/{services/config/config.js → config.js} +7 -1
  35. package/dist/create/CreateApp.js +153 -0
  36. package/dist/create/ai-generate.js +147 -0
  37. package/dist/create/generate.js +73 -0
  38. package/dist/create/steps/ApiKeyStep.js +97 -0
  39. package/dist/create/steps/AvatarStep.js +16 -0
  40. package/dist/create/steps/BioStep.js +14 -0
  41. package/dist/create/steps/DoneStep.js +14 -0
  42. package/dist/create/steps/IdentityStep.js +163 -0
  43. package/dist/create/steps/NameStep.js +71 -0
  44. package/dist/create/steps/ScaffoldStep.js +58 -0
  45. package/dist/create/steps/SoulStep.js +58 -0
  46. package/dist/create/steps/StrategyStep.js +58 -0
  47. package/dist/create/validate-api-key.js +47 -0
  48. package/dist/create/welcome.js +304 -0
  49. package/dist/list/ListApp.js +79 -0
  50. package/dist/{services/agent/env.js → load-agent-env.js} +1 -1
  51. package/dist/migrate-templates/MigrateApp.js +131 -0
  52. package/dist/migrate-templates/migrate.js +86 -0
  53. package/dist/presets.js +613 -0
  54. package/dist/start/AgentProcessManager.js +98 -0
  55. package/dist/start/Dashboard.js +92 -0
  56. package/dist/start/SelectAgentApp.js +81 -0
  57. package/dist/start/StartApp.js +189 -0
  58. package/dist/start/patch-headless.js +101 -0
  59. package/dist/start/patch-managed-mode.js +142 -0
  60. package/dist/start/start-command.js +24 -0
  61. package/dist/theme.js +54 -0
  62. package/package.json +2 -2
  63. package/dist/CLAUDE.md +0 -7
  64. package/dist/backtest/CLAUDE.md +0 -7
  65. package/dist/cli.js +0 -20
  66. package/dist/services/config/constant.js +0 -8
  67. package/dist/shared/agent/config.js +0 -75
  68. package/dist/shared/agent/env.js +0 -30
  69. package/dist/shared/agent/helpers/model.js +0 -92
  70. package/dist/shared/ai-providers.js +0 -66
  71. /package/dist/{services/agent/prompts → agent}/chat-prompt.js +0 -0
  72. /package/dist/{services/agent → agent}/config.js +0 -0
  73. /package/dist/{services/agent/tools → agent}/edit-section.js +0 -0
  74. /package/dist/{services/agent/tools → agent}/fetch-rules.js +0 -0
  75. /package/dist/{services/agent → agent}/helpers.js +0 -0
  76. /package/dist/{services/agent/skills/types.js → agent/objects.js} +0 -0
  77. /package/dist/{services/agent → agent}/skills/index.js +0 -0
  78. /package/dist/{services/agent → agent}/skills/skill-parser.js +0 -0
  79. /package/dist/{services/agent → agent/skills}/types.js +0 -0
  80. /package/dist/{services/agent → agent}/tools/index.js +0 -0
  81. /package/dist/{services/agent → agent}/tools/market/index.js +0 -0
  82. /package/dist/{services/agent → agent}/tools/market/tools.js +0 -0
  83. /package/dist/{services/agent → agent}/tools/mindshare/index.js +0 -0
  84. /package/dist/{services/agent → agent}/tools/mindshare/tools.js +0 -0
  85. /package/dist/{services/agent → agent}/tools/read-skill-tool.js +0 -0
  86. /package/dist/{services/agent → agent}/tools/ta/index.js +0 -0
  87. /package/dist/{services/agent → agent}/tools/ta/indicators.js +0 -0
  88. /package/dist/{services/ai-providers.js → ai-providers.js} +0 -0
@@ -0,0 +1,142 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ const TEMPLATES_DIR = path.resolve(__dirname, '../../templates');
6
+ const MANAGED_MARKER = 'HIVE_MANAGED';
7
+ const INTERACTIVE_GUARD_MARKER = 'Input Bar — only when stdin is a real TTY';
8
+ /**
9
+ * Patches an existing agent to support:
10
+ * 1. Headless mode — skip TextInput when stdin is not a TTY (piped by dashboard)
11
+ * 2. Managed mode — Escape exits back to dashboard when HIVE_MANAGED=1
12
+ *
13
+ * Idempotent — skips if already patched.
14
+ */
15
+ export async function ensureManagedMode(agentDir) {
16
+ await patchProcessLifecycle(agentDir);
17
+ await patchIndexTsx(agentDir);
18
+ }
19
+ async function patchProcessLifecycle(agentDir) {
20
+ const filePath = path.join(agentDir, 'process-lifecycle.ts');
21
+ const exists = await fs.pathExists(filePath);
22
+ if (!exists) {
23
+ return;
24
+ }
25
+ const content = await fs.readFile(filePath, 'utf-8');
26
+ // Already patched if gracefulShutdown is exported
27
+ if (content.includes('export const gracefulShutdown')) {
28
+ return;
29
+ }
30
+ // Copy the template version (it has the export + exitCode param)
31
+ const templatePath = path.join(TEMPLATES_DIR, 'process-lifecycle.ts');
32
+ const templateExists = await fs.pathExists(templatePath);
33
+ if (!templateExists) {
34
+ return;
35
+ }
36
+ await fs.copyFile(templatePath, filePath);
37
+ }
38
+ async function patchIndexTsx(agentDir) {
39
+ const filePath = path.join(agentDir, 'index.tsx');
40
+ const exists = await fs.pathExists(filePath);
41
+ if (!exists) {
42
+ return;
43
+ }
44
+ let content = await fs.readFile(filePath, 'utf-8');
45
+ // Already fully patched
46
+ if (content.includes(MANAGED_MARKER) && content.includes(INTERACTIVE_GUARD_MARKER)) {
47
+ return;
48
+ }
49
+ // 1. Add useInput to ink import if not present
50
+ // Existing pattern: "import { render, Box, Text } from 'ink';"
51
+ // Target: "import { render, Box, Text, useInput } from 'ink';"
52
+ if (!content.includes('useInput')) {
53
+ content = content.replace(/\s*}\s*from\s*'ink'/, ", useInput } from 'ink'");
54
+ }
55
+ // 2. Add gracefulShutdown import from process-lifecycle if not present
56
+ if (!content.includes('gracefulShutdown')) {
57
+ content = content.replace(/import\s*\{\s*setupProcessLifecycle\s*\}\s*from\s*'\.\/process-lifecycle'/, "import { setupProcessLifecycle, gracefulShutdown } from './process-lifecycle'");
58
+ }
59
+ // 3. Add isInteractive + managed mode hooks after useAgent() destructuring
60
+ if (!content.includes(MANAGED_MARKER)) {
61
+ const useAgentClose = '} = useAgent();';
62
+ const hookInsertIndex = content.indexOf(useAgentClose);
63
+ if (hookInsertIndex === -1) {
64
+ return;
65
+ }
66
+ const insertAfter = hookInsertIndex + useAgentClose.length;
67
+ const hooks = `
68
+
69
+ // When stdin is not a TTY (piped by hive-cli start), skip interactive input
70
+ const isInteractive = process.stdin.isTTY === true;
71
+
72
+ // When managed by hive-cli start, Escape exits back to dashboard
73
+ const isManaged = process.env.HIVE_MANAGED === '1';
74
+
75
+ useInput((_input, key) => {
76
+ if (isManaged && key.escape) {
77
+ void gracefulShutdown(0);
78
+ }
79
+ }, { isActive: isManaged });`;
80
+ content = content.slice(0, insertAfter) + hooks + content.slice(insertAfter);
81
+ }
82
+ // 4. Wrap TextInput in isInteractive guard
83
+ // Replace the input bar section to conditionally render TextInput
84
+ if (!content.includes(INTERACTIVE_GUARD_MARKER) && content.includes('<TextInput')) {
85
+ // Find the Input Bar comment and replace the entire block
86
+ const inputBarStart = content.indexOf('{/* Input Bar */}');
87
+ if (inputBarStart === -1) {
88
+ // Fallback: just wrap the TextInput line
89
+ await fs.writeFile(filePath, content, 'utf-8');
90
+ return;
91
+ }
92
+ // Find the closing </Box> of the bottom border (3 Box elements after Input Bar comment)
93
+ // Pattern: <Box> separator </Box> <Box> TextInput </Box> <Box> bottom border </Box>
94
+ let pos = inputBarStart;
95
+ let boxCloseCount = 0;
96
+ const boxCloseTag = '</Box>';
97
+ while (boxCloseCount < 3 && pos < content.length) {
98
+ const nextClose = content.indexOf(boxCloseTag, pos);
99
+ if (nextClose === -1) {
100
+ break;
101
+ }
102
+ pos = nextClose + boxCloseTag.length;
103
+ boxCloseCount++;
104
+ }
105
+ if (boxCloseCount === 3) {
106
+ const inputBarEnd = pos;
107
+ const replacement = `{/* Input Bar — only when stdin is a real TTY */}
108
+ <Box>
109
+ <Text color="gray">
110
+ {isInteractive ? border.teeLeft : border.bottomLeft}
111
+ {border.horizontal.repeat(boxWidth - 2)}
112
+ {isInteractive ? border.teeRight : border.bottomRight}
113
+ </Text>
114
+ </Box>
115
+ {isInteractive && (
116
+ <>
117
+ <Box paddingLeft={1}>
118
+ <Text color={colors.honey}>{symbols.arrow} </Text>
119
+ <TextInput
120
+ value={input}
121
+ onChange={setInput}
122
+ onSubmit={(val) => {
123
+ setInput('');
124
+ void handleChatSubmit(val);
125
+ }}
126
+ placeholder={chatStreaming ? 'thinking...' : \`chat with \${agentName} agent...\`}
127
+ />
128
+ </Box>
129
+ <Box>
130
+ <Text color="gray">
131
+ {border.bottomLeft}
132
+ {border.horizontal.repeat(boxWidth - 2)}
133
+ {border.bottomRight}
134
+ </Text>
135
+ </Box>
136
+ </>
137
+ )}`;
138
+ content = content.slice(0, inputBarStart) + replacement + content.slice(inputBarEnd);
139
+ }
140
+ }
141
+ await fs.writeFile(filePath, content, 'utf-8');
142
+ }
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { render } from 'ink';
3
+ import { showWelcome } from '../create/welcome.js';
4
+ import { scanAgents, fetchBulkStats, sortAgentsByHoney } from '../agents.js';
5
+ import { symbols, styled } from '../theme.js';
6
+ import { AgentProcessManager } from './AgentProcessManager.js';
7
+ import { Dashboard } from './Dashboard.js';
8
+ export async function startCommand() {
9
+ // Run welcome animation and scan agents in parallel
10
+ const results = await Promise.all([showWelcome(), scanAgents()]);
11
+ const discovered = results[1];
12
+ if (discovered.length === 0) {
13
+ console.log(`\n ${styled.honey(symbols.hive)} ${styled.red('No agents found in ~/.hive/agents/')}\n`);
14
+ console.log(` ${styled.gray('Create agents with:')} ${styled.white('npx @hive-org/cli@latest create')}\n`);
15
+ return;
16
+ }
17
+ const names = discovered.map((a) => a.name);
18
+ const statsMap = await fetchBulkStats(names);
19
+ const sortedDiscovered = sortAgentsByHoney(discovered, statsMap);
20
+ const manager = new AgentProcessManager();
21
+ manager.spawnAll(sortedDiscovered);
22
+ const { waitUntilExit } = render(React.createElement(Dashboard, { manager, statsMap }));
23
+ await waitUntilExit();
24
+ }
package/dist/theme.js ADDED
@@ -0,0 +1,54 @@
1
+ import chalk from 'chalk';
2
+ export const colors = {
3
+ honey: '#F5A623',
4
+ honeyDark: '#D4891A',
5
+ honeyBright: '#FFD700',
6
+ white: '#FFFFFF',
7
+ gray: '#A6A6A6',
8
+ grayDim: '#555555',
9
+ green: '#27C587',
10
+ red: '#E14B4B',
11
+ wax: '#C45C5C',
12
+ cyan: '#22D3EE',
13
+ hot: '#FB923C',
14
+ controversial: '#C084FC',
15
+ };
16
+ export const symbols = {
17
+ hive: '\u2B21', // ⬡
18
+ diamond: '\u25C6', // ◆
19
+ diamondOpen: '\u25C7', // ◇
20
+ dot: '\u25CF', // ●
21
+ spinner: ['\u25D0', '\u25D3', '\u25D1', '\u25D2'], // ◐ ◓ ◑ ◒
22
+ check: '\u2713', // ✓
23
+ cross: '\u2717', // ✗
24
+ arrow: '\u203A', // ›
25
+ };
26
+ export const border = {
27
+ horizontal: '\u2500', // ─
28
+ vertical: '\u2502', // │
29
+ topLeft: '\u250C', // ┌
30
+ topRight: '\u2510', // ┐
31
+ bottomLeft: '\u2514', // └
32
+ bottomRight: '\u2518', // ┘
33
+ teeLeft: '\u251C', // ├
34
+ teeRight: '\u2524', // ┤
35
+ };
36
+ export const animation = {
37
+ DATA_CHARS: '01▪▫░▒',
38
+ HEX_CHARS: '⬡⬢',
39
+ TICK_MS: 120,
40
+ };
41
+ export const styled = {
42
+ honey: (text) => chalk.hex(colors.honey)(text),
43
+ honeyBold: (text) => chalk.hex(colors.honey).bold(text),
44
+ white: (text) => chalk.white(text),
45
+ whiteBold: (text) => chalk.bold.white(text),
46
+ gray: (text) => chalk.gray(text),
47
+ dim: (text) => chalk.hex(colors.grayDim)(text),
48
+ green: (text) => chalk.hex(colors.green)(text),
49
+ red: (text) => chalk.hex(colors.red)(text),
50
+ wax: (text) => chalk.hex(colors.wax)(text),
51
+ cyan: (text) => chalk.hex(colors.cyan)(text),
52
+ hot: (text) => chalk.hex(colors.hot)(text),
53
+ controversial: (text) => chalk.hex(colors.controversial)(text),
54
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhive/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "CLI for bootstrapping zHive AI Agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,7 +31,7 @@
31
31
  "@ai-sdk/openai": "^3.0.25",
32
32
  "@ai-sdk/xai": "^3.0.0",
33
33
  "@openrouter/ai-sdk-provider": "^0.4.0",
34
- "@zhive/sdk": "^0.5.2",
34
+ "@zhive/sdk": "^0.5.3",
35
35
  "ai": "^6.0.71",
36
36
  "axios": "^1.6.0",
37
37
  "chalk": "^5.3.0",
package/dist/CLAUDE.md DELETED
@@ -1,7 +0,0 @@
1
- <claude-mem-context>
2
- # Recent Activity
3
-
4
- <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5
-
6
- *No recent activity*
7
- </claude-mem-context>
@@ -1,7 +0,0 @@
1
- <claude-mem-context>
2
- # Recent Activity
3
-
4
- <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5
-
6
- *No recent activity*
7
- </claude-mem-context>
package/dist/cli.js DELETED
@@ -1,20 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import { createRequire } from 'module';
4
- import { createCreateCommand } from './commands/create/commands/index.js';
5
- import { createListCommand } from './commands/list/commands/index.js';
6
- import { createStartCommand } from './commands/start/commands/index.js';
7
- import { createStartAllCommand } from './commands/start-all/commands/index.js';
8
- import { createRunCommand } from './commands/run/commands/index.js';
9
- import { createMigrateTemplatesCommand } from './commands/migrate-templates/commands/index.js';
10
- const require = createRequire(import.meta.url);
11
- const packageJson = require('../package.json');
12
- const program = new Command();
13
- program.name('@zhive/cli').version(packageJson.version);
14
- program.addCommand(createCreateCommand());
15
- program.addCommand(createListCommand());
16
- program.addCommand(createStartCommand());
17
- program.addCommand(createStartAllCommand());
18
- program.addCommand(createRunCommand());
19
- program.addCommand(createMigrateTemplatesCommand());
20
- program.parse(process.argv);
@@ -1,8 +0,0 @@
1
- import path from 'path';
2
- import os from 'os';
3
- export const HIVE_API_URL = 'https://api.zhive.ai';
4
- export const HIVE_FRONTEND_URL = 'https://www.zhive.ai';
5
- export function getHiveDir() {
6
- const hiveDir = path.join(os.homedir(), '.hive');
7
- return hiveDir;
8
- }
@@ -1,75 +0,0 @@
1
- import * as fs from 'fs/promises';
2
- import * as path from 'path';
3
- async function loadMarkdownFile(filename) {
4
- const filePath = path.join(process.cwd(), filename);
5
- const content = await fs.readFile(filePath, 'utf-8');
6
- return content;
7
- }
8
- function extractField(content, pattern) {
9
- const match = content.match(pattern);
10
- if (match === null) {
11
- return null;
12
- }
13
- const value = match[1].trim();
14
- return value;
15
- }
16
- const VALID_SENTIMENTS = [
17
- 'very-bullish',
18
- 'bullish',
19
- 'neutral',
20
- 'bearish',
21
- 'very-bearish',
22
- ];
23
- const VALID_TIMEFRAMES = ['1h', '4h', '24h'];
24
- function parseSentiment(raw) {
25
- if (raw !== null && VALID_SENTIMENTS.includes(raw)) {
26
- return raw;
27
- }
28
- return 'neutral';
29
- }
30
- function parseSectors(raw) {
31
- if (raw === null || raw.trim() === '') {
32
- return [];
33
- }
34
- const sectors = raw
35
- .split(',')
36
- .map((s) => s.trim())
37
- .filter((s) => s.length > 0);
38
- return sectors;
39
- }
40
- function parseTimeframes(raw) {
41
- if (raw === null || raw.trim() === '') {
42
- return ['1h', '4h', '24h'];
43
- }
44
- const parsed = raw
45
- .split(',')
46
- .map((t) => t.trim())
47
- .filter((t) => VALID_TIMEFRAMES.includes(t));
48
- if (parsed.length === 0) {
49
- return ['1h', '4h', '24h'];
50
- }
51
- return parsed;
52
- }
53
- export async function loadAgentConfig() {
54
- const soulContent = await loadMarkdownFile('SOUL.md');
55
- const strategyContent = await loadMarkdownFile('STRATEGY.md');
56
- const name = extractField(soulContent, /^#\s+Agent:\s+(.+)$/m);
57
- if (name === null) {
58
- throw new Error('Could not parse agent name from SOUL.md. Expected "# Agent: <name>" as the first heading.');
59
- }
60
- const avatarUrl = extractField(soulContent, /^## Avatar\s*\n+(https?:\/\/.+)$/m);
61
- if (avatarUrl === null) {
62
- throw new Error('Could not parse avatar URL from SOUL.md. Expected a valid URL under "## Avatar".');
63
- }
64
- const bioRaw = extractField(soulContent, /^## Bio\s*\n+(.+)$/m);
65
- const bio = bioRaw ?? null;
66
- const sentimentRaw = extractField(strategyContent, /^-\s+Bias:\s+(.+)$/m);
67
- const sectorsRaw = extractField(strategyContent, /^-\s+Sectors:\s+(.+)$/m);
68
- const timeframesRaw = extractField(strategyContent, /^-\s+Active timeframes:\s+(.+)$/m);
69
- const agentProfile = {
70
- sentiment: parseSentiment(sentimentRaw),
71
- sectors: parseSectors(sectorsRaw),
72
- timeframes: parseTimeframes(timeframesRaw),
73
- };
74
- return { name, bio, avatarUrl, soulContent, strategyContent, agentProfile };
75
- }
@@ -1,30 +0,0 @@
1
- import { readFileSync } from 'fs';
2
- import { AI_PROVIDER_ENV_VARS } from '../ai-providers.js';
3
- let _agentProviderKeys = new Set();
4
- /**
5
- * Provider env-var names declared in the agent's .env file.
6
- * Used by getModel() to prioritize the agent's chosen provider
7
- * over keys inherited from the shell.
8
- */
9
- export function getAgentProviderKeys() {
10
- return _agentProviderKeys;
11
- }
12
- /**
13
- * Load the agent's .env with provider-key priority.
14
- *
15
- * 1. Parse .env to discover which provider keys the agent declared.
16
- * 2. Load .env with override so the agent's values win for the same key.
17
- * 3. getModel() uses getAgentProviderKeys() to check those providers first,
18
- * falling back to shell-inherited keys if the agent has none.
19
- */
20
- export async function loadAgentEnv() {
21
- try {
22
- const content = readFileSync('.env', 'utf-8');
23
- _agentProviderKeys = new Set(AI_PROVIDER_ENV_VARS.filter((key) => new RegExp(`^${key}=`, 'm').test(content)));
24
- }
25
- catch {
26
- _agentProviderKeys = new Set();
27
- }
28
- const { config } = await import('dotenv');
29
- config({ override: true });
30
- }
@@ -1,92 +0,0 @@
1
- import { AI_PROVIDERS } from '../../ai-providers.js';
2
- import { getAgentProviderKeys } from '../../config/env-loader.js';
3
- const PROVIDERS = [
4
- {
5
- label: 'Anthropic',
6
- envVar: 'ANTHROPIC_API_KEY',
7
- load: async (modelId) => {
8
- const { anthropic } = await import('@ai-sdk/anthropic');
9
- return anthropic(modelId);
10
- },
11
- },
12
- {
13
- label: 'OpenAI',
14
- envVar: 'OPENAI_API_KEY',
15
- load: async (modelId) => {
16
- const { openai } = await import('@ai-sdk/openai');
17
- return openai(modelId);
18
- },
19
- },
20
- {
21
- label: 'Google',
22
- envVar: 'GOOGLE_GENERATIVE_AI_API_KEY',
23
- load: async (modelId) => {
24
- const { google } = await import('@ai-sdk/google');
25
- return google(modelId);
26
- },
27
- },
28
- {
29
- label: 'xAI',
30
- envVar: 'XAI_API_KEY',
31
- load: async (modelId) => {
32
- const { xai } = await import('@ai-sdk/xai');
33
- return xai(modelId);
34
- },
35
- },
36
- {
37
- label: 'OpenRouter',
38
- envVar: 'OPENROUTER_API_KEY',
39
- load: async (modelId) => {
40
- const { createOpenRouter } = await import('@openrouter/ai-sdk-provider');
41
- const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
42
- return openrouter.chat(modelId);
43
- },
44
- },
45
- ];
46
- export function resolveModelInfo() {
47
- const overrideModel = process.env.HIVE_MODEL;
48
- const agentKeys = getAgentProviderKeys();
49
- const sortedProviders = [
50
- ...PROVIDERS.filter((p) => agentKeys.has(p.envVar)),
51
- ...PROVIDERS.filter((p) => !agentKeys.has(p.envVar)),
52
- ];
53
- for (const provider of sortedProviders) {
54
- const keyValue = process.env[provider.envVar];
55
- if (keyValue && keyValue.trim().length > 0) {
56
- const centralProvider = AI_PROVIDERS.find((p) => p.envVar === provider.envVar);
57
- const runtimeModel = centralProvider?.models.runtime ?? 'unknown';
58
- const modelId = overrideModel ?? runtimeModel;
59
- const source = agentKeys.has(provider.envVar) ? '.env' : 'shell';
60
- return { provider: provider.label, modelId, source };
61
- }
62
- }
63
- return { provider: 'unknown', modelId: 'unknown', source: 'unknown' };
64
- }
65
- let _modelPromise = null;
66
- export function getModel() {
67
- if (_modelPromise) {
68
- return _modelPromise;
69
- }
70
- _modelPromise = (async () => {
71
- const info = resolveModelInfo();
72
- if (info.provider === 'unknown') {
73
- throw new Error('No AI provider API key found in environment. ' +
74
- 'Set one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY');
75
- }
76
- const agentKeys = getAgentProviderKeys();
77
- const sortedProviders = [
78
- ...PROVIDERS.filter((p) => agentKeys.has(p.envVar)),
79
- ...PROVIDERS.filter((p) => !agentKeys.has(p.envVar)),
80
- ];
81
- for (const provider of sortedProviders) {
82
- const keyValue = process.env[provider.envVar];
83
- if (keyValue && keyValue.trim().length > 0) {
84
- const modelId = info.modelId;
85
- const model = await provider.load(modelId);
86
- return model;
87
- }
88
- }
89
- throw new Error('Unreachable: resolveModelInfo succeeded but no provider found');
90
- })();
91
- return _modelPromise;
92
- }
@@ -1,66 +0,0 @@
1
- export const AI_PROVIDERS = [
2
- {
3
- id: 'openai',
4
- label: 'OpenAI',
5
- package: '@ai-sdk/openai',
6
- envVar: 'OPENAI_API_KEY',
7
- models: { validation: 'gpt-4o-mini', generation: 'gpt-5-mini', runtime: 'gpt-5-mini' },
8
- },
9
- {
10
- id: 'anthropic',
11
- label: 'Anthropic',
12
- package: '@ai-sdk/anthropic',
13
- envVar: 'ANTHROPIC_API_KEY',
14
- models: {
15
- validation: 'claude-haiku-4-5-20251001',
16
- generation: 'claude-haiku-4-5',
17
- runtime: 'claude-haiku-4-5',
18
- },
19
- },
20
- {
21
- id: 'google',
22
- label: 'Google',
23
- package: '@ai-sdk/google',
24
- envVar: 'GOOGLE_GENERATIVE_AI_API_KEY',
25
- models: {
26
- validation: 'gemini-2.0-flash',
27
- generation: 'gemini-3-flash-preview',
28
- runtime: 'gemini-3-flash-preview',
29
- },
30
- },
31
- {
32
- id: 'xai',
33
- label: 'xAI',
34
- package: '@ai-sdk/xai',
35
- envVar: 'XAI_API_KEY',
36
- models: {
37
- validation: 'grok-2',
38
- generation: 'grok-4-1-fast-reasoning',
39
- runtime: 'grok-4-1-fast-reasoning',
40
- },
41
- },
42
- {
43
- id: 'openrouter',
44
- label: 'OpenRouter',
45
- package: '@openrouter/ai-sdk-provider',
46
- envVar: 'OPENROUTER_API_KEY',
47
- models: {
48
- validation: 'openai/gpt-4o-mini',
49
- generation: 'openai/gpt-5.1-mini',
50
- runtime: 'openai/gpt-5.1-mini',
51
- },
52
- },
53
- ];
54
- /**
55
- * All env-var names used by AI providers.
56
- * Used to clear shell-inherited keys before loading an agent's .env,
57
- * so only the agent's chosen provider is active.
58
- */
59
- export const AI_PROVIDER_ENV_VARS = AI_PROVIDERS.map((p) => p.envVar);
60
- export function getProvider(id) {
61
- const provider = AI_PROVIDERS.find((p) => p.id === id);
62
- if (!provider) {
63
- throw new Error(`Unknown AI provider: ${id}`);
64
- }
65
- return provider;
66
- }
File without changes
File without changes
File without changes
File without changes
File without changes