@antseed/cli 0.1.0
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/.env.example +15 -0
- package/README.md +169 -0
- package/dist/cli/commands/balance.d.ts +3 -0
- package/dist/cli/commands/balance.d.ts.map +1 -0
- package/dist/cli/commands/balance.js +64 -0
- package/dist/cli/commands/balance.js.map +1 -0
- package/dist/cli/commands/browse.d.ts +7 -0
- package/dist/cli/commands/browse.d.ts.map +1 -0
- package/dist/cli/commands/browse.js +100 -0
- package/dist/cli/commands/browse.js.map +1 -0
- package/dist/cli/commands/config.d.ts +20 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +239 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/connect.d.ts +14 -0
- package/dist/cli/commands/connect.d.ts.map +1 -0
- package/dist/cli/commands/connect.js +298 -0
- package/dist/cli/commands/connect.js.map +1 -0
- package/dist/cli/commands/connect.test.d.ts +2 -0
- package/dist/cli/commands/connect.test.d.ts.map +1 -0
- package/dist/cli/commands/connect.test.js +54 -0
- package/dist/cli/commands/connect.test.js.map +1 -0
- package/dist/cli/commands/dashboard.d.ts +6 -0
- package/dist/cli/commands/dashboard.d.ts.map +1 -0
- package/dist/cli/commands/dashboard.js +48 -0
- package/dist/cli/commands/dashboard.js.map +1 -0
- package/dist/cli/commands/deposit.d.ts +3 -0
- package/dist/cli/commands/deposit.d.ts.map +1 -0
- package/dist/cli/commands/deposit.js +48 -0
- package/dist/cli/commands/deposit.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +3 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +94 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +91 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/plugin-create.d.ts +11 -0
- package/dist/cli/commands/plugin-create.d.ts.map +1 -0
- package/dist/cli/commands/plugin-create.js +201 -0
- package/dist/cli/commands/plugin-create.js.map +1 -0
- package/dist/cli/commands/plugin-create.test.d.ts +2 -0
- package/dist/cli/commands/plugin-create.test.d.ts.map +1 -0
- package/dist/cli/commands/plugin-create.test.js +53 -0
- package/dist/cli/commands/plugin-create.test.js.map +1 -0
- package/dist/cli/commands/plugin.d.ts +3 -0
- package/dist/cli/commands/plugin.d.ts.map +1 -0
- package/dist/cli/commands/plugin.js +279 -0
- package/dist/cli/commands/plugin.js.map +1 -0
- package/dist/cli/commands/plugin.test.d.ts +2 -0
- package/dist/cli/commands/plugin.test.d.ts.map +1 -0
- package/dist/cli/commands/plugin.test.js +53 -0
- package/dist/cli/commands/plugin.test.js.map +1 -0
- package/dist/cli/commands/profile.d.ts +10 -0
- package/dist/cli/commands/profile.d.ts.map +1 -0
- package/dist/cli/commands/profile.js +89 -0
- package/dist/cli/commands/profile.js.map +1 -0
- package/dist/cli/commands/seed.d.ts +11 -0
- package/dist/cli/commands/seed.d.ts.map +1 -0
- package/dist/cli/commands/seed.js +397 -0
- package/dist/cli/commands/seed.js.map +1 -0
- package/dist/cli/commands/seed.test.d.ts +2 -0
- package/dist/cli/commands/seed.test.d.ts.map +1 -0
- package/dist/cli/commands/seed.test.js +57 -0
- package/dist/cli/commands/seed.test.js.map +1 -0
- package/dist/cli/commands/status.d.ts +8 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +55 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/types.d.ts +14 -0
- package/dist/cli/commands/types.d.ts.map +1 -0
- package/dist/cli/commands/types.js +41 -0
- package/dist/cli/commands/types.js.map +1 -0
- package/dist/cli/commands/withdraw.d.ts +3 -0
- package/dist/cli/commands/withdraw.d.ts.map +1 -0
- package/dist/cli/commands/withdraw.js +48 -0
- package/dist/cli/commands/withdraw.js.map +1 -0
- package/dist/cli/formatters.d.ts +29 -0
- package/dist/cli/formatters.d.ts.map +1 -0
- package/dist/cli/formatters.js +67 -0
- package/dist/cli/formatters.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +41 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/shutdown.d.ts +11 -0
- package/dist/cli/shutdown.d.ts.map +1 -0
- package/dist/cli/shutdown.js +34 -0
- package/dist/cli/shutdown.js.map +1 -0
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +48 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/effective.d.ts +26 -0
- package/dist/config/effective.d.ts.map +1 -0
- package/dist/config/effective.js +84 -0
- package/dist/config/effective.js.map +1 -0
- package/dist/config/effective.test.d.ts +2 -0
- package/dist/config/effective.test.d.ts.map +1 -0
- package/dist/config/effective.test.js +65 -0
- package/dist/config/effective.test.js.map +1 -0
- package/dist/config/loader.d.ts +12 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +212 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/loader.test.d.ts +2 -0
- package/dist/config/loader.test.d.ts.map +1 -0
- package/dist/config/loader.test.js +77 -0
- package/dist/config/loader.test.js.map +1 -0
- package/dist/config/types.d.ts +133 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/validation.d.ts +10 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +50 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/env/load-env.d.ts +6 -0
- package/dist/env/load-env.d.ts.map +1 -0
- package/dist/env/load-env.js +18 -0
- package/dist/env/load-env.js.map +1 -0
- package/dist/plugins/loader.d.ts +7 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +70 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/manager.d.ts +11 -0
- package/dist/plugins/manager.d.ts.map +1 -0
- package/dist/plugins/manager.js +52 -0
- package/dist/plugins/manager.js.map +1 -0
- package/dist/plugins/registry.d.ts +8 -0
- package/dist/plugins/registry.d.ts.map +1 -0
- package/dist/plugins/registry.js +39 -0
- package/dist/plugins/registry.js.map +1 -0
- package/dist/proxy/buyer-proxy.d.ts +30 -0
- package/dist/proxy/buyer-proxy.d.ts.map +1 -0
- package/dist/proxy/buyer-proxy.js +488 -0
- package/dist/proxy/buyer-proxy.js.map +1 -0
- package/dist/status/node-status.d.ts +22 -0
- package/dist/status/node-status.d.ts.map +1 -0
- package/dist/status/node-status.js +83 -0
- package/dist/status/node-status.js.map +1 -0
- package/package.json +39 -0
- package/src/cli/commands/balance.ts +77 -0
- package/src/cli/commands/browse.ts +113 -0
- package/src/cli/commands/config.ts +271 -0
- package/src/cli/commands/connect.test.ts +69 -0
- package/src/cli/commands/connect.ts +342 -0
- package/src/cli/commands/dashboard.ts +59 -0
- package/src/cli/commands/deposit.ts +61 -0
- package/src/cli/commands/dev.ts +107 -0
- package/src/cli/commands/init.ts +99 -0
- package/src/cli/commands/plugin-create.test.ts +60 -0
- package/src/cli/commands/plugin-create.ts +230 -0
- package/src/cli/commands/plugin.test.ts +55 -0
- package/src/cli/commands/plugin.ts +295 -0
- package/src/cli/commands/profile.ts +95 -0
- package/src/cli/commands/seed.test.ts +70 -0
- package/src/cli/commands/seed.ts +447 -0
- package/src/cli/commands/status.ts +73 -0
- package/src/cli/commands/types.ts +56 -0
- package/src/cli/commands/withdraw.ts +61 -0
- package/src/cli/formatters.ts +64 -0
- package/src/cli/index.ts +46 -0
- package/src/cli/shutdown.ts +38 -0
- package/src/config/defaults.ts +49 -0
- package/src/config/effective.test.ts +80 -0
- package/src/config/effective.ts +119 -0
- package/src/config/loader.test.ts +95 -0
- package/src/config/loader.ts +251 -0
- package/src/config/types.ts +139 -0
- package/src/config/validation.ts +78 -0
- package/src/env/load-env.ts +20 -0
- package/src/plugins/loader.ts +96 -0
- package/src/plugins/manager.ts +66 -0
- package/src/plugins/registry.ts +45 -0
- package/src/proxy/buyer-proxy.ts +604 -0
- package/src/status/node-status.ts +105 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format a USD earnings string with a dollar sign and green color.
|
|
5
|
+
* @param amountUSD - Amount as string (e.g., "1.234567")
|
|
6
|
+
* @returns Formatted string like "$1.23"
|
|
7
|
+
*/
|
|
8
|
+
export function formatEarnings(amountUSD: string): string {
|
|
9
|
+
const num = parseFloat(amountUSD);
|
|
10
|
+
return chalk.green(`$${num.toFixed(2)}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Format a token count with K/M suffix.
|
|
15
|
+
* @param tokens - Number of tokens
|
|
16
|
+
* @returns Formatted string like "1.5K" or "2.3M"
|
|
17
|
+
*/
|
|
18
|
+
export function formatTokens(tokens: number): string {
|
|
19
|
+
if (tokens >= 1_000_000) {
|
|
20
|
+
return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
21
|
+
}
|
|
22
|
+
if (tokens >= 1_000) {
|
|
23
|
+
return `${(tokens / 1_000).toFixed(1)}K`;
|
|
24
|
+
}
|
|
25
|
+
return String(tokens);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Format a capacity usage percentage with color coding.
|
|
30
|
+
* Green < 50%, Yellow 50-80%, Red > 80%
|
|
31
|
+
*/
|
|
32
|
+
export function formatCapacity(percent: number): string {
|
|
33
|
+
const label = `${percent.toFixed(0)}%`;
|
|
34
|
+
if (percent < 50) return chalk.green(label);
|
|
35
|
+
if (percent < 80) return chalk.yellow(label);
|
|
36
|
+
return chalk.red(label);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Format a duration in milliseconds to a human-readable string.
|
|
41
|
+
* @param ms - Duration in milliseconds
|
|
42
|
+
* @returns String like "2h 15m" or "45s" or "3d 1h"
|
|
43
|
+
*/
|
|
44
|
+
export function formatDuration(ms: number): string {
|
|
45
|
+
const seconds = Math.floor(ms / 1000);
|
|
46
|
+
if (seconds < 60) return `${seconds}s`;
|
|
47
|
+
const minutes = Math.floor(seconds / 60);
|
|
48
|
+
if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
|
|
49
|
+
const hours = Math.floor(minutes / 60);
|
|
50
|
+
if (hours < 24) return `${hours}h ${minutes % 60}m`;
|
|
51
|
+
const days = Math.floor(hours / 24);
|
|
52
|
+
return `${days}d ${hours % 24}h`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Format a reputation score (0-1) with color coding.
|
|
57
|
+
* Green >= 0.8, Yellow >= 0.5, Red < 0.5
|
|
58
|
+
*/
|
|
59
|
+
export function formatReputation(score: number): string {
|
|
60
|
+
const label = `${(score * 100).toFixed(0)}%`;
|
|
61
|
+
if (score >= 0.8) return chalk.green(label);
|
|
62
|
+
if (score >= 0.5) return chalk.yellow(label);
|
|
63
|
+
return chalk.red(label);
|
|
64
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { loadEnvFromFiles } from '../env/load-env.js';
|
|
5
|
+
import { registerSeedCommand } from './commands/seed.js';
|
|
6
|
+
import { registerConnectCommand } from './commands/connect.js';
|
|
7
|
+
import { registerStatusCommand } from './commands/status.js';
|
|
8
|
+
import { registerConfigCommand } from './commands/config.js';
|
|
9
|
+
import { registerDashboardCommand } from './commands/dashboard.js';
|
|
10
|
+
import { registerDevCommand } from './commands/dev.js';
|
|
11
|
+
import { registerBrowseCommand } from './commands/browse.js';
|
|
12
|
+
import { registerInitCommand } from './commands/init.js';
|
|
13
|
+
import { registerPluginCommand } from './commands/plugin.js';
|
|
14
|
+
import { registerProfileCommand, registerPeerCommand } from './commands/profile.js';
|
|
15
|
+
import { registerDepositCommand } from './commands/deposit.js';
|
|
16
|
+
import { registerWithdrawCommand } from './commands/withdraw.js';
|
|
17
|
+
import { registerBalanceCommand } from './commands/balance.js';
|
|
18
|
+
|
|
19
|
+
loadEnvFromFiles();
|
|
20
|
+
|
|
21
|
+
const program = new Command();
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.name('antseed')
|
|
25
|
+
.description('P2P marketplace for reselling idle LLM plan capacity')
|
|
26
|
+
.version('0.1.0')
|
|
27
|
+
.option('-c, --config <path>', 'path to config file', '~/.antseed/config.json')
|
|
28
|
+
.option('--data-dir <path>', 'path to node identity/state directory', '~/.antseed')
|
|
29
|
+
.option('-v, --verbose', 'enable verbose logging', false);
|
|
30
|
+
|
|
31
|
+
registerSeedCommand(program);
|
|
32
|
+
registerConnectCommand(program);
|
|
33
|
+
registerStatusCommand(program);
|
|
34
|
+
registerConfigCommand(program);
|
|
35
|
+
registerDashboardCommand(program);
|
|
36
|
+
registerDevCommand(program);
|
|
37
|
+
registerBrowseCommand(program);
|
|
38
|
+
registerInitCommand(program);
|
|
39
|
+
registerPluginCommand(program);
|
|
40
|
+
registerProfileCommand(program);
|
|
41
|
+
registerPeerCommand(program);
|
|
42
|
+
registerDepositCommand(program);
|
|
43
|
+
registerWithdrawCommand(program);
|
|
44
|
+
registerBalanceCommand(program);
|
|
45
|
+
|
|
46
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type ShutdownCallback = () => Promise<void>;
|
|
2
|
+
|
|
3
|
+
let shutdownCallbacks: ShutdownCallback[] = [];
|
|
4
|
+
let isShuttingDown = false;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register a callback to be called on shutdown (SIGINT/SIGTERM).
|
|
8
|
+
* Multiple callbacks are called in LIFO order (last registered = first called).
|
|
9
|
+
* The process exits after all callbacks complete.
|
|
10
|
+
*
|
|
11
|
+
* @param callback - Async function to run during shutdown
|
|
12
|
+
*/
|
|
13
|
+
export function setupShutdownHandler(callback: ShutdownCallback): void {
|
|
14
|
+
shutdownCallbacks.push(callback);
|
|
15
|
+
|
|
16
|
+
// Only register signal handlers once
|
|
17
|
+
if (shutdownCallbacks.length === 1) {
|
|
18
|
+
const handler = async () => {
|
|
19
|
+
if (isShuttingDown) return; // Prevent double-shutdown
|
|
20
|
+
isShuttingDown = true;
|
|
21
|
+
|
|
22
|
+
// Execute callbacks in LIFO order
|
|
23
|
+
const callbacks = [...shutdownCallbacks].reverse();
|
|
24
|
+
for (const cb of callbacks) {
|
|
25
|
+
try {
|
|
26
|
+
await cb();
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error('Shutdown error:', (err as Error).message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
process.exit(0);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
process.on('SIGINT', handler);
|
|
36
|
+
process.on('SIGTERM', handler);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AntseedConfig } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a default Antseed configuration with sensible defaults.
|
|
5
|
+
*/
|
|
6
|
+
export function createDefaultConfig(): AntseedConfig {
|
|
7
|
+
return {
|
|
8
|
+
identity: {
|
|
9
|
+
displayName: 'Antseed Node',
|
|
10
|
+
},
|
|
11
|
+
providers: [],
|
|
12
|
+
seller: {
|
|
13
|
+
reserveFloor: 10,
|
|
14
|
+
maxConcurrentBuyers: 5,
|
|
15
|
+
enabledProviders: [],
|
|
16
|
+
pricing: {
|
|
17
|
+
defaults: {
|
|
18
|
+
inputUsdPerMillion: 10,
|
|
19
|
+
outputUsdPerMillion: 10,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
buyer: {
|
|
24
|
+
preferredProviders: ['anthropic', 'openai', 'claude-code', 'claude-oauth', 'openrouter', 'local-llm'],
|
|
25
|
+
maxPricing: {
|
|
26
|
+
defaults: {
|
|
27
|
+
inputUsdPerMillion: 100,
|
|
28
|
+
outputUsdPerMillion: 100,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
minPeerReputation: 50,
|
|
32
|
+
proxyPort: 8377,
|
|
33
|
+
},
|
|
34
|
+
payments: {
|
|
35
|
+
preferredMethod: 'crypto',
|
|
36
|
+
platformFeeRate: 0.05,
|
|
37
|
+
crypto: {
|
|
38
|
+
chainId: 'base-local',
|
|
39
|
+
rpcUrl: 'http://127.0.0.1:8545',
|
|
40
|
+
escrowContractAddress: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
|
|
41
|
+
usdcContractAddress: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512',
|
|
42
|
+
defaultLockAmountUSDC: '1',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
network: {
|
|
46
|
+
bootstrapNodes: [],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createDefaultConfig } from './defaults.js';
|
|
4
|
+
import {
|
|
5
|
+
resolveEffectiveBuyerConfig,
|
|
6
|
+
resolveEffectiveRoleConfig,
|
|
7
|
+
resolveEffectiveSellerConfig,
|
|
8
|
+
} from './effective.js';
|
|
9
|
+
|
|
10
|
+
test('effective seller config precedence is flags > env > config > defaults', () => {
|
|
11
|
+
const config = createDefaultConfig();
|
|
12
|
+
config.seller.pricing.defaults.inputUsdPerMillion = 10;
|
|
13
|
+
config.seller.pricing.defaults.outputUsdPerMillion = 20;
|
|
14
|
+
|
|
15
|
+
const env = {
|
|
16
|
+
ANTSEED_SELLER_INPUT_USD_PER_MILLION: '30',
|
|
17
|
+
ANTSEED_SELLER_OUTPUT_USD_PER_MILLION: '40',
|
|
18
|
+
} as NodeJS.ProcessEnv;
|
|
19
|
+
|
|
20
|
+
const effective = resolveEffectiveSellerConfig({
|
|
21
|
+
config,
|
|
22
|
+
env,
|
|
23
|
+
sellerOverrides: {
|
|
24
|
+
inputUsdPerMillion: 50,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
assert.equal(effective.pricing.defaults.inputUsdPerMillion, 50);
|
|
29
|
+
assert.equal(effective.pricing.defaults.outputUsdPerMillion, 40);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('effective buyer config precedence is flags > env > config > defaults', () => {
|
|
33
|
+
const config = createDefaultConfig();
|
|
34
|
+
config.buyer.minPeerReputation = 25;
|
|
35
|
+
config.buyer.maxPricing.defaults.inputUsdPerMillion = 70;
|
|
36
|
+
config.buyer.maxPricing.defaults.outputUsdPerMillion = 80;
|
|
37
|
+
|
|
38
|
+
const env = {
|
|
39
|
+
ANTSEED_BUYER_MIN_REPUTATION: '45',
|
|
40
|
+
ANTSEED_BUYER_PREFERRED_PROVIDERS: 'openai,anthropic',
|
|
41
|
+
ANTSEED_BUYER_MAX_INPUT_USD_PER_MILLION: '90',
|
|
42
|
+
ANTSEED_BUYER_MAX_OUTPUT_USD_PER_MILLION: '95',
|
|
43
|
+
} as NodeJS.ProcessEnv;
|
|
44
|
+
|
|
45
|
+
const effective = resolveEffectiveBuyerConfig({
|
|
46
|
+
config,
|
|
47
|
+
env,
|
|
48
|
+
buyerOverrides: {
|
|
49
|
+
minPeerReputation: 55,
|
|
50
|
+
preferredProviders: ['anthropic', 'openai'],
|
|
51
|
+
maxOutputUsdPerMillion: 99,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
assert.equal(effective.minPeerReputation, 55);
|
|
56
|
+
assert.deepEqual(effective.preferredProviders, ['anthropic', 'openai']);
|
|
57
|
+
assert.equal(effective.maxPricing.defaults.inputUsdPerMillion, 90);
|
|
58
|
+
assert.equal(effective.maxPricing.defaults.outputUsdPerMillion, 99);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('effective config resolution does not mutate loaded config', () => {
|
|
62
|
+
const config = createDefaultConfig();
|
|
63
|
+
const original = JSON.parse(JSON.stringify(config));
|
|
64
|
+
|
|
65
|
+
const env = {
|
|
66
|
+
ANTSEED_BUYER_MAX_INPUT_USD_PER_MILLION: '123',
|
|
67
|
+
} as NodeJS.ProcessEnv;
|
|
68
|
+
|
|
69
|
+
const effective = resolveEffectiveRoleConfig({
|
|
70
|
+
config,
|
|
71
|
+
env,
|
|
72
|
+
sellerOverrides: {
|
|
73
|
+
outputUsdPerMillion: 44,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
assert.equal(effective.buyer.maxPricing.defaults.inputUsdPerMillion, 123);
|
|
78
|
+
assert.equal(effective.seller.pricing.defaults.outputUsdPerMillion, 44);
|
|
79
|
+
assert.deepEqual(config, original);
|
|
80
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { BuyerCLIConfig, AntseedConfig, SellerCLIConfig } from './types.js';
|
|
2
|
+
|
|
3
|
+
export interface SellerRuntimeOverrides {
|
|
4
|
+
reserveFloor?: number;
|
|
5
|
+
inputUsdPerMillion?: number;
|
|
6
|
+
outputUsdPerMillion?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface BuyerRuntimeOverrides {
|
|
10
|
+
proxyPort?: number;
|
|
11
|
+
minPeerReputation?: number;
|
|
12
|
+
preferredProviders?: string[];
|
|
13
|
+
maxInputUsdPerMillion?: number;
|
|
14
|
+
maxOutputUsdPerMillion?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ResolveEffectiveConfigInput {
|
|
18
|
+
config: AntseedConfig;
|
|
19
|
+
env?: NodeJS.ProcessEnv;
|
|
20
|
+
sellerOverrides?: SellerRuntimeOverrides;
|
|
21
|
+
buyerOverrides?: BuyerRuntimeOverrides;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseEnvNumber(env: NodeJS.ProcessEnv, key: string): number | undefined {
|
|
25
|
+
const raw = env[key];
|
|
26
|
+
if (raw === undefined) return undefined;
|
|
27
|
+
const parsed = Number(raw);
|
|
28
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseEnvProviders(env: NodeJS.ProcessEnv, key: string): string[] | undefined {
|
|
32
|
+
const raw = env[key];
|
|
33
|
+
if (!raw) return undefined;
|
|
34
|
+
const list = raw
|
|
35
|
+
.split(',')
|
|
36
|
+
.map((entry) => entry.trim())
|
|
37
|
+
.filter((entry) => entry.length > 0);
|
|
38
|
+
return list.length > 0 ? list : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function resolveEffectiveSellerConfig(input: ResolveEffectiveConfigInput): SellerCLIConfig {
|
|
42
|
+
const env = input.env ?? process.env;
|
|
43
|
+
const seller = structuredClone(input.config.seller);
|
|
44
|
+
|
|
45
|
+
const envInputUsdPerMillion = parseEnvNumber(env, 'ANTSEED_SELLER_INPUT_USD_PER_MILLION');
|
|
46
|
+
const envOutputUsdPerMillion = parseEnvNumber(env, 'ANTSEED_SELLER_OUTPUT_USD_PER_MILLION');
|
|
47
|
+
|
|
48
|
+
if (envInputUsdPerMillion !== undefined) {
|
|
49
|
+
seller.pricing.defaults.inputUsdPerMillion = envInputUsdPerMillion;
|
|
50
|
+
}
|
|
51
|
+
if (envOutputUsdPerMillion !== undefined) {
|
|
52
|
+
seller.pricing.defaults.outputUsdPerMillion = envOutputUsdPerMillion;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const overrides = input.sellerOverrides;
|
|
56
|
+
if (overrides?.reserveFloor !== undefined) {
|
|
57
|
+
seller.reserveFloor = overrides.reserveFloor;
|
|
58
|
+
}
|
|
59
|
+
if (overrides?.inputUsdPerMillion !== undefined) {
|
|
60
|
+
seller.pricing.defaults.inputUsdPerMillion = overrides.inputUsdPerMillion;
|
|
61
|
+
}
|
|
62
|
+
if (overrides?.outputUsdPerMillion !== undefined) {
|
|
63
|
+
seller.pricing.defaults.outputUsdPerMillion = overrides.outputUsdPerMillion;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return seller;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function resolveEffectiveBuyerConfig(input: ResolveEffectiveConfigInput): BuyerCLIConfig {
|
|
70
|
+
const env = input.env ?? process.env;
|
|
71
|
+
const buyer = structuredClone(input.config.buyer);
|
|
72
|
+
|
|
73
|
+
const envMinReputation = parseEnvNumber(env, 'ANTSEED_BUYER_MIN_REPUTATION');
|
|
74
|
+
const envPreferredProviders = parseEnvProviders(env, 'ANTSEED_BUYER_PREFERRED_PROVIDERS');
|
|
75
|
+
const envMaxInputUsdPerMillion = parseEnvNumber(env, 'ANTSEED_BUYER_MAX_INPUT_USD_PER_MILLION');
|
|
76
|
+
const envMaxOutputUsdPerMillion = parseEnvNumber(env, 'ANTSEED_BUYER_MAX_OUTPUT_USD_PER_MILLION');
|
|
77
|
+
|
|
78
|
+
if (envMinReputation !== undefined) {
|
|
79
|
+
buyer.minPeerReputation = envMinReputation;
|
|
80
|
+
}
|
|
81
|
+
if (envPreferredProviders) {
|
|
82
|
+
buyer.preferredProviders = envPreferredProviders;
|
|
83
|
+
}
|
|
84
|
+
if (envMaxInputUsdPerMillion !== undefined) {
|
|
85
|
+
buyer.maxPricing.defaults.inputUsdPerMillion = envMaxInputUsdPerMillion;
|
|
86
|
+
}
|
|
87
|
+
if (envMaxOutputUsdPerMillion !== undefined) {
|
|
88
|
+
buyer.maxPricing.defaults.outputUsdPerMillion = envMaxOutputUsdPerMillion;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const overrides = input.buyerOverrides;
|
|
92
|
+
if (overrides?.proxyPort !== undefined) {
|
|
93
|
+
buyer.proxyPort = overrides.proxyPort;
|
|
94
|
+
}
|
|
95
|
+
if (overrides?.minPeerReputation !== undefined) {
|
|
96
|
+
buyer.minPeerReputation = overrides.minPeerReputation;
|
|
97
|
+
}
|
|
98
|
+
if (overrides?.preferredProviders && overrides.preferredProviders.length > 0) {
|
|
99
|
+
buyer.preferredProviders = [...overrides.preferredProviders];
|
|
100
|
+
}
|
|
101
|
+
if (overrides?.maxInputUsdPerMillion !== undefined) {
|
|
102
|
+
buyer.maxPricing.defaults.inputUsdPerMillion = overrides.maxInputUsdPerMillion;
|
|
103
|
+
}
|
|
104
|
+
if (overrides?.maxOutputUsdPerMillion !== undefined) {
|
|
105
|
+
buyer.maxPricing.defaults.outputUsdPerMillion = overrides.maxOutputUsdPerMillion;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return buyer;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function resolveEffectiveRoleConfig(input: ResolveEffectiveConfigInput): {
|
|
112
|
+
seller: SellerCLIConfig;
|
|
113
|
+
buyer: BuyerCLIConfig;
|
|
114
|
+
} {
|
|
115
|
+
return {
|
|
116
|
+
seller: resolveEffectiveSellerConfig(input),
|
|
117
|
+
buyer: resolveEffectiveBuyerConfig(input),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { loadConfig } from './loader.js';
|
|
7
|
+
|
|
8
|
+
async function withTempConfig(contents: string, fn: (configPath: string) => Promise<void>): Promise<void> {
|
|
9
|
+
const dir = await mkdtemp(join(tmpdir(), 'antseed-cli-config-'));
|
|
10
|
+
const configPath = join(dir, 'config.json');
|
|
11
|
+
try {
|
|
12
|
+
await writeFile(configPath, contents, 'utf-8');
|
|
13
|
+
await fn(configPath);
|
|
14
|
+
} finally {
|
|
15
|
+
await rm(dir, { recursive: true, force: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test('loadConfig deep-merges nested hierarchical pricing without dropping defaults', async () => {
|
|
20
|
+
await withTempConfig(
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
seller: {
|
|
23
|
+
pricing: {
|
|
24
|
+
providers: {
|
|
25
|
+
anthropic: {
|
|
26
|
+
models: {
|
|
27
|
+
'claude-sonnet-4-5-20250929': {
|
|
28
|
+
inputUsdPerMillion: 12,
|
|
29
|
+
outputUsdPerMillion: 18,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
buyer: {
|
|
37
|
+
maxPricing: {
|
|
38
|
+
providers: {
|
|
39
|
+
openai: {
|
|
40
|
+
defaults: {
|
|
41
|
+
inputUsdPerMillion: 55,
|
|
42
|
+
outputUsdPerMillion: 77,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
async (configPath) => {
|
|
50
|
+
const config = await loadConfig(configPath);
|
|
51
|
+
|
|
52
|
+
assert.equal(config.seller.pricing.defaults.inputUsdPerMillion, 10);
|
|
53
|
+
assert.equal(config.seller.pricing.defaults.outputUsdPerMillion, 10);
|
|
54
|
+
assert.equal(
|
|
55
|
+
config.seller.pricing.providers?.anthropic?.models?.['claude-sonnet-4-5-20250929']?.inputUsdPerMillion,
|
|
56
|
+
12
|
|
57
|
+
);
|
|
58
|
+
assert.equal(
|
|
59
|
+
config.seller.pricing.providers?.anthropic?.models?.['claude-sonnet-4-5-20250929']?.outputUsdPerMillion,
|
|
60
|
+
18
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
assert.equal(config.buyer.maxPricing.defaults.inputUsdPerMillion, 100);
|
|
64
|
+
assert.equal(config.buyer.maxPricing.defaults.outputUsdPerMillion, 100);
|
|
65
|
+
assert.equal(config.buyer.maxPricing.providers?.openai?.defaults?.inputUsdPerMillion, 55);
|
|
66
|
+
assert.equal(config.buyer.maxPricing.providers?.openai?.defaults?.outputUsdPerMillion, 77);
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('loadConfig throws explicit validation error for incomplete model pricing', async () => {
|
|
72
|
+
await withTempConfig(
|
|
73
|
+
JSON.stringify({
|
|
74
|
+
seller: {
|
|
75
|
+
pricing: {
|
|
76
|
+
providers: {
|
|
77
|
+
anthropic: {
|
|
78
|
+
models: {
|
|
79
|
+
broken: {
|
|
80
|
+
inputUsdPerMillion: 12,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
async (configPath) => {
|
|
89
|
+
await assert.rejects(
|
|
90
|
+
async () => loadConfig(configPath),
|
|
91
|
+
/seller\.pricing\.providers\.anthropic\.models\.broken\.outputUsdPerMillion/
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
});
|