@hivemind-os/collective-mcp-server 0.2.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/.turbo/turbo-build.log +14 -0
- package/dist/index.d.ts +493 -0
- package/dist/index.js +2129 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -0
- package/src/context.ts +58 -0
- package/src/encryption.ts +25 -0
- package/src/index.ts +41 -0
- package/src/resources/agent.ts +77 -0
- package/src/resources/capabilities.ts +24 -0
- package/src/resources/index.ts +70 -0
- package/src/resources/task.ts +58 -0
- package/src/resources/wallet.ts +32 -0
- package/src/tools/analytics.ts +122 -0
- package/src/tools/balance.ts +36 -0
- package/src/tools/deactivate.ts +33 -0
- package/src/tools/discover.ts +256 -0
- package/src/tools/dispute.ts +135 -0
- package/src/tools/execute-async.ts +39 -0
- package/src/tools/execute.ts +418 -0
- package/src/tools/index.ts +152 -0
- package/src/tools/indexer-client.ts +163 -0
- package/src/tools/marketplace-accept-bid.ts +43 -0
- package/src/tools/marketplace-bid.ts +56 -0
- package/src/tools/marketplace-browse.ts +66 -0
- package/src/tools/marketplace-post.ts +96 -0
- package/src/tools/metering.ts +214 -0
- package/src/tools/multi-execute.ts +218 -0
- package/src/tools/policy-update.ts +94 -0
- package/src/tools/register.ts +78 -0
- package/src/tools/relay-registry.ts +95 -0
- package/src/tools/stake.ts +103 -0
- package/src/tools/task-history.ts +86 -0
- package/src/tools/task-status.ts +66 -0
- package/tests/analytics.test.ts +41 -0
- package/tests/auth-errors.test.ts +85 -0
- package/tests/balance.test.ts +32 -0
- package/tests/context.test.ts +112 -0
- package/tests/discover.test.ts +207 -0
- package/tests/dispute.test.ts +140 -0
- package/tests/execute.test.ts +150 -0
- package/tests/marketplace.test.ts +117 -0
- package/tests/metering.test.ts +173 -0
- package/tests/multi-execute.test.ts +123 -0
- package/tests/relay-registry.test.ts +71 -0
- package/tests/stake.test.ts +90 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +10 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
|
|
4
|
+
import { SessionExpiredError } from '@hivemind-os/collective-core';
|
|
5
|
+
|
|
6
|
+
import type { MeshToolContext } from '../context.js';
|
|
7
|
+
import { meshAnalyticsTool, runMeshAnalytics } from './analytics.js';
|
|
8
|
+
import { meshBalanceTool, runMeshBalance } from './balance.js';
|
|
9
|
+
import { meshDeactivateTool, runMeshDeactivate } from './deactivate.js';
|
|
10
|
+
import { meshDiscoverTool, runMeshDiscover } from './discover.js';
|
|
11
|
+
import { meshDisputeTool, runMeshDispute } from './dispute.js';
|
|
12
|
+
import { meshExecuteAsyncTool, runMeshExecuteAsync } from './execute-async.js';
|
|
13
|
+
import { meshExecuteTool, runMeshExecute } from './execute.js';
|
|
14
|
+
import { meshMeteredExecuteTool, meshVerifyResultTool, runMeshMeteredExecute, runMeshVerifyResult } from './metering.js';
|
|
15
|
+
import { meshMarketplaceAcceptBidTool, runMeshMarketplaceAcceptBid } from './marketplace-accept-bid.js';
|
|
16
|
+
import { meshMarketplaceBidTool, runMeshMarketplaceBid } from './marketplace-bid.js';
|
|
17
|
+
import { meshMarketplaceBrowseTool, runMeshMarketplaceBrowse } from './marketplace-browse.js';
|
|
18
|
+
import { meshMarketplacePostTool, runMeshMarketplacePost } from './marketplace-post.js';
|
|
19
|
+
import { meshMultiExecuteTool, runMeshMultiExecute } from './multi-execute.js';
|
|
20
|
+
import { meshPolicyUpdateTool, runMeshPolicyUpdate } from './policy-update.js';
|
|
21
|
+
import { meshRegisterTool, runMeshRegister } from './register.js';
|
|
22
|
+
import { meshRelayRegistryTool, runMeshRelayRegistry } from './relay-registry.js';
|
|
23
|
+
import { meshStakeTool, runMeshStake } from './stake.js';
|
|
24
|
+
import { meshTaskHistoryTool, runMeshTaskHistory } from './task-history.js';
|
|
25
|
+
import { meshTaskStatusTool, runMeshTaskStatus } from './task-status.js';
|
|
26
|
+
|
|
27
|
+
const toolDefinitions = [
|
|
28
|
+
meshAnalyticsTool,
|
|
29
|
+
meshDiscoverTool,
|
|
30
|
+
meshDisputeTool,
|
|
31
|
+
meshExecuteTool,
|
|
32
|
+
meshExecuteAsyncTool,
|
|
33
|
+
meshMeteredExecuteTool,
|
|
34
|
+
meshVerifyResultTool,
|
|
35
|
+
meshMarketplacePostTool,
|
|
36
|
+
meshMarketplaceBrowseTool,
|
|
37
|
+
meshMultiExecuteTool,
|
|
38
|
+
meshMarketplaceBidTool,
|
|
39
|
+
meshMarketplaceAcceptBidTool,
|
|
40
|
+
meshTaskStatusTool,
|
|
41
|
+
meshRegisterTool,
|
|
42
|
+
meshDeactivateTool,
|
|
43
|
+
meshBalanceTool,
|
|
44
|
+
meshPolicyUpdateTool,
|
|
45
|
+
meshStakeTool,
|
|
46
|
+
meshRelayRegistryTool,
|
|
47
|
+
meshTaskHistoryTool,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export type MeshToolHandler = (params: never, context: MeshToolContext) => Promise<unknown>;
|
|
51
|
+
|
|
52
|
+
export const meshToolHandlers: Record<string, MeshToolHandler> = {
|
|
53
|
+
[meshAnalyticsTool.name]: runMeshAnalytics,
|
|
54
|
+
[meshDiscoverTool.name]: runMeshDiscover,
|
|
55
|
+
[meshDisputeTool.name]: runMeshDispute,
|
|
56
|
+
[meshExecuteTool.name]: runMeshExecute,
|
|
57
|
+
[meshExecuteAsyncTool.name]: runMeshExecuteAsync,
|
|
58
|
+
[meshMeteredExecuteTool.name]: runMeshMeteredExecute,
|
|
59
|
+
[meshVerifyResultTool.name]: runMeshVerifyResult,
|
|
60
|
+
[meshMarketplacePostTool.name]: runMeshMarketplacePost,
|
|
61
|
+
[meshMarketplaceBrowseTool.name]: runMeshMarketplaceBrowse,
|
|
62
|
+
[meshMultiExecuteTool.name]: runMeshMultiExecute,
|
|
63
|
+
[meshMarketplaceBidTool.name]: runMeshMarketplaceBid,
|
|
64
|
+
[meshMarketplaceAcceptBidTool.name]: runMeshMarketplaceAcceptBid,
|
|
65
|
+
[meshTaskStatusTool.name]: runMeshTaskStatus,
|
|
66
|
+
[meshRegisterTool.name]: runMeshRegister,
|
|
67
|
+
[meshDeactivateTool.name]: runMeshDeactivate,
|
|
68
|
+
[meshBalanceTool.name]: runMeshBalance,
|
|
69
|
+
[meshPolicyUpdateTool.name]: runMeshPolicyUpdate,
|
|
70
|
+
[meshStakeTool.name]: runMeshStake,
|
|
71
|
+
[meshRelayRegistryTool.name]: runMeshRelayRegistry,
|
|
72
|
+
[meshTaskHistoryTool.name]: runMeshTaskHistory,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function registerToolHandlers(server: Server, context: MeshToolContext): void {
|
|
76
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
77
|
+
tools: toolDefinitions,
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
81
|
+
const handler = meshToolHandlers[request.params.name];
|
|
82
|
+
if (!handler) {
|
|
83
|
+
return createErrorResult(`Unknown tool: ${request.params.name}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const response = await handler((request.params.arguments ?? {}) as never, context);
|
|
88
|
+
return createSuccessResult(response);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const message =
|
|
91
|
+
error instanceof SessionExpiredError
|
|
92
|
+
? 'Authentication expired. Please re-authenticate via the daemon portal.'
|
|
93
|
+
: error instanceof Error
|
|
94
|
+
? error.message
|
|
95
|
+
: String(error);
|
|
96
|
+
const sessionState = await getSessionState(context);
|
|
97
|
+
return createErrorResult(
|
|
98
|
+
sessionState ? `${message} (session state: ${sessionState})` : message,
|
|
99
|
+
sessionState ? { session_state: sessionState, reauth_required: error instanceof SessionExpiredError } : undefined,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const meshToolDefinitions = toolDefinitions;
|
|
106
|
+
|
|
107
|
+
function createSuccessResult(payload: unknown): { content: Array<{ type: 'text'; text: string }> } {
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: 'text',
|
|
112
|
+
text: serialize(payload),
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createErrorResult(
|
|
119
|
+
message: string,
|
|
120
|
+
details?: Record<string, unknown>,
|
|
121
|
+
): {
|
|
122
|
+
isError: true;
|
|
123
|
+
content: Array<{ type: 'text'; text: string }>;
|
|
124
|
+
} {
|
|
125
|
+
return {
|
|
126
|
+
isError: true,
|
|
127
|
+
content: [
|
|
128
|
+
{
|
|
129
|
+
type: 'text',
|
|
130
|
+
text: serialize({ error: message, ...details }),
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function getSessionState(context: MeshToolContext): Promise<string | null> {
|
|
137
|
+
const provider = context.authProvider;
|
|
138
|
+
if (!provider?.getSessionState) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const state = await provider.getSessionState();
|
|
143
|
+
return typeof state === 'string' ? state : null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function serialize(payload: unknown): string {
|
|
147
|
+
return JSON.stringify(payload, bigintReplacer, 2);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function bigintReplacer(_key: string, value: unknown): unknown {
|
|
151
|
+
return typeof value === 'bigint' ? value.toString() : value;
|
|
152
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { AgentCard, Capability } from '@hivemind-os/collective-types';
|
|
2
|
+
|
|
3
|
+
import type { MeshToolContext } from '../context.js';
|
|
4
|
+
|
|
5
|
+
interface GraphQLResponse<T> {
|
|
6
|
+
data?: T;
|
|
7
|
+
errors?: Array<{ message?: string }>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface GraphQLAgentNode {
|
|
11
|
+
id: string;
|
|
12
|
+
owner: string;
|
|
13
|
+
did: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
endpoint?: string | null;
|
|
17
|
+
active: boolean;
|
|
18
|
+
version: number;
|
|
19
|
+
registeredAt: string;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
totalTasksCompleted: number;
|
|
22
|
+
totalTasksFailed: number;
|
|
23
|
+
totalTasksDisputed: number;
|
|
24
|
+
totalEarningsMist: string;
|
|
25
|
+
hasStake: boolean;
|
|
26
|
+
stakeMist?: string | null;
|
|
27
|
+
stakeType?: 'agent' | 'relay' | null;
|
|
28
|
+
capabilities: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
version: string;
|
|
32
|
+
executionMode?: 'sync' | 'async' | null;
|
|
33
|
+
paymentRails?: string[] | null;
|
|
34
|
+
pricing: {
|
|
35
|
+
rail: Capability['pricing']['rail'];
|
|
36
|
+
amount: string;
|
|
37
|
+
currency: string;
|
|
38
|
+
};
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function executeIndexerQuery<TData>(
|
|
43
|
+
context: Pick<MeshToolContext, 'indexer'>,
|
|
44
|
+
query: string,
|
|
45
|
+
variables?: Record<string, unknown>,
|
|
46
|
+
): Promise<TData> {
|
|
47
|
+
const endpoint = resolveIndexerUrl(context);
|
|
48
|
+
if (!endpoint) {
|
|
49
|
+
throw new Error('Indexer is not configured.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const fetchImpl = context.indexer?.fetch ?? globalThis.fetch;
|
|
53
|
+
const response = await fetchImpl(endpoint, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
'content-type': 'application/json',
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({ query, variables }),
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`Indexer query failed with status ${response.status}.`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const payload = (await response.json()) as GraphQLResponse<TData>;
|
|
65
|
+
if (payload.errors && payload.errors.length > 0) {
|
|
66
|
+
throw new Error(payload.errors.map((entry) => entry.message ?? 'Unknown GraphQL error').join('; '));
|
|
67
|
+
}
|
|
68
|
+
if (!payload.data) {
|
|
69
|
+
throw new Error('Indexer query returned no data.');
|
|
70
|
+
}
|
|
71
|
+
return payload.data;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function queryIndexerAgents(
|
|
75
|
+
context: Pick<MeshToolContext, 'indexer'>,
|
|
76
|
+
params: { capability: string; limit: number; minReputation?: number; category?: string },
|
|
77
|
+
): Promise<AgentCard[]> {
|
|
78
|
+
const data = await executeIndexerQuery<{ agents: { nodes: GraphQLAgentNode[] } }>(
|
|
79
|
+
context,
|
|
80
|
+
`query IndexedAgents($capability: String, $limit: Int, $minReputation: Float, $category: String) {
|
|
81
|
+
agents(capability: $capability, limit: $limit, minReputation: $minReputation, category: $category) {
|
|
82
|
+
nodes {
|
|
83
|
+
id
|
|
84
|
+
owner
|
|
85
|
+
did
|
|
86
|
+
name
|
|
87
|
+
description
|
|
88
|
+
endpoint
|
|
89
|
+
active
|
|
90
|
+
version
|
|
91
|
+
registeredAt
|
|
92
|
+
updatedAt
|
|
93
|
+
totalTasksCompleted
|
|
94
|
+
totalTasksFailed
|
|
95
|
+
totalTasksDisputed
|
|
96
|
+
totalEarningsMist
|
|
97
|
+
hasStake
|
|
98
|
+
stakeMist
|
|
99
|
+
stakeType
|
|
100
|
+
capabilities {
|
|
101
|
+
name
|
|
102
|
+
description
|
|
103
|
+
version
|
|
104
|
+
executionMode
|
|
105
|
+
paymentRails
|
|
106
|
+
pricing {
|
|
107
|
+
rail
|
|
108
|
+
amount
|
|
109
|
+
currency
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}`,
|
|
115
|
+
{
|
|
116
|
+
capability: params.capability,
|
|
117
|
+
limit: params.limit,
|
|
118
|
+
minReputation: params.minReputation,
|
|
119
|
+
category: params.category,
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return data.agents.nodes.map(mapGraphqlAgent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function resolveIndexerUrl(context: Pick<MeshToolContext, 'indexer'>): string | null {
|
|
127
|
+
const value = context.indexer?.graphqlUrl ?? process.env.COLLECTIVE_INDEXER_URL;
|
|
128
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function mapGraphqlAgent(agent: GraphQLAgentNode): AgentCard {
|
|
132
|
+
return {
|
|
133
|
+
id: agent.id,
|
|
134
|
+
owner: agent.owner,
|
|
135
|
+
did: agent.did as AgentCard['did'],
|
|
136
|
+
name: agent.name,
|
|
137
|
+
description: agent.description,
|
|
138
|
+
endpoint: agent.endpoint ?? undefined,
|
|
139
|
+
active: agent.active,
|
|
140
|
+
version: agent.version,
|
|
141
|
+
registeredAt: Number(agent.registeredAt),
|
|
142
|
+
updatedAt: Number(agent.updatedAt),
|
|
143
|
+
totalTasksCompleted: agent.totalTasksCompleted,
|
|
144
|
+
totalTasksFailed: agent.totalTasksFailed,
|
|
145
|
+
totalTasksDisputed: agent.totalTasksDisputed,
|
|
146
|
+
totalEarningsMist: BigInt(agent.totalEarningsMist),
|
|
147
|
+
hasStake: agent.hasStake,
|
|
148
|
+
stakeMist: agent.stakeMist ? BigInt(agent.stakeMist) : undefined,
|
|
149
|
+
stakeType: agent.stakeType ?? undefined,
|
|
150
|
+
capabilities: agent.capabilities.map((capability) => ({
|
|
151
|
+
name: capability.name,
|
|
152
|
+
description: capability.description,
|
|
153
|
+
version: capability.version,
|
|
154
|
+
executionMode: capability.executionMode ?? undefined,
|
|
155
|
+
paymentRails: capability.paymentRails ?? undefined,
|
|
156
|
+
pricing: {
|
|
157
|
+
rail: capability.pricing.rail,
|
|
158
|
+
amount: BigInt(capability.pricing.amount),
|
|
159
|
+
currency: capability.pricing.currency,
|
|
160
|
+
},
|
|
161
|
+
})),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { MarketplaceClient } from '@hivemind-os/collective-core';
|
|
2
|
+
|
|
3
|
+
import type { MeshToolContext } from '../context.js';
|
|
4
|
+
|
|
5
|
+
export interface MeshMarketplaceAcceptBidParams {
|
|
6
|
+
task_id: string;
|
|
7
|
+
bid_id: string;
|
|
8
|
+
reject_competing?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const meshMarketplaceAcceptBidTool = {
|
|
12
|
+
name: 'collective_marketplace_accept_bid',
|
|
13
|
+
description: 'Accept a marketplace bid and optionally reject competing bids',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: 'object' as const,
|
|
16
|
+
properties: {
|
|
17
|
+
task_id: { type: 'string', description: 'Marketplace task id' },
|
|
18
|
+
bid_id: { type: 'string', description: 'Bid id to accept' },
|
|
19
|
+
reject_competing: { type: 'boolean', description: 'Reject other active bids in the same transaction (default true)' },
|
|
20
|
+
},
|
|
21
|
+
required: ['task_id', 'bid_id'],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export async function runMeshMarketplaceAcceptBid(
|
|
26
|
+
params: MeshMarketplaceAcceptBidParams,
|
|
27
|
+
context: MeshToolContext,
|
|
28
|
+
): Promise<Record<string, unknown>> {
|
|
29
|
+
const client = context.marketplaceClient ?? new MarketplaceClient(context.suiClient, context.networkConfig);
|
|
30
|
+
const result = await client.acceptBid({
|
|
31
|
+
taskId: params.task_id,
|
|
32
|
+
bidId: params.bid_id,
|
|
33
|
+
rejectCompeting: params.reject_competing,
|
|
34
|
+
signer: context.keypair,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
task_id: params.task_id,
|
|
39
|
+
bid_id: params.bid_id,
|
|
40
|
+
rejected_bid_ids: result.rejectedBidIds,
|
|
41
|
+
tx_digest: result.txDigest,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { MarketplaceClient } from '@hivemind-os/collective-core';
|
|
2
|
+
|
|
3
|
+
import type { MeshToolContext } from '../context.js';
|
|
4
|
+
|
|
5
|
+
export interface MeshMarketplaceBidParams {
|
|
6
|
+
task_id: string;
|
|
7
|
+
bid_price_mist: string | number;
|
|
8
|
+
reputation_score?: string | number;
|
|
9
|
+
evidence?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const meshMarketplaceBidTool = {
|
|
13
|
+
name: 'collective_marketplace_bid',
|
|
14
|
+
description: 'Place a marketplace bid on an open task',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object' as const,
|
|
17
|
+
properties: {
|
|
18
|
+
task_id: { type: 'string', description: 'Open task object id' },
|
|
19
|
+
bid_price_mist: { type: 'string', description: 'Bid price in MIST' },
|
|
20
|
+
reputation_score: { type: 'string', description: 'Optional reputation score override' },
|
|
21
|
+
evidence: { type: 'string', description: 'Optional proposal or qualifications blob content' },
|
|
22
|
+
},
|
|
23
|
+
required: ['task_id', 'bid_price_mist'],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function runMeshMarketplaceBid(
|
|
28
|
+
params: MeshMarketplaceBidParams,
|
|
29
|
+
context: MeshToolContext,
|
|
30
|
+
): Promise<Record<string, unknown>> {
|
|
31
|
+
const client = context.marketplaceClient ?? new MarketplaceClient(context.suiClient, context.networkConfig);
|
|
32
|
+
const result = await client.placeBid({
|
|
33
|
+
taskId: params.task_id,
|
|
34
|
+
bidPriceMist: parseMist(params.bid_price_mist, 'bid_price_mist'),
|
|
35
|
+
reputationScore: params.reputation_score == null ? undefined : parseMist(params.reputation_score, 'reputation_score'),
|
|
36
|
+
evidenceBlob: params.evidence,
|
|
37
|
+
signer: context.keypair,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
bid_id: result.bidId,
|
|
42
|
+
task_id: params.task_id,
|
|
43
|
+
tx_digest: result.txDigest,
|
|
44
|
+
reputation_score: result.reputationScore.toString(),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseMist(value: string | number, field: string): bigint {
|
|
49
|
+
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
|
|
50
|
+
return BigInt(value);
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === 'string' && /^\d+$/.test(value.trim())) {
|
|
53
|
+
return BigInt(value.trim());
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`${field} must be a non-negative integer string.`);
|
|
56
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { MarketplaceClient } from '@hivemind-os/collective-core';
|
|
2
|
+
|
|
3
|
+
import type { MeshToolContext } from '../context.js';
|
|
4
|
+
|
|
5
|
+
export interface MeshMarketplaceBrowseParams {
|
|
6
|
+
category?: string;
|
|
7
|
+
min_price_mist?: string | number;
|
|
8
|
+
max_price_mist?: string | number;
|
|
9
|
+
limit?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const meshMarketplaceBrowseTool = {
|
|
13
|
+
name: 'collective_marketplace_browse',
|
|
14
|
+
description: 'Browse open marketplace tasks by category and price filters',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object' as const,
|
|
17
|
+
properties: {
|
|
18
|
+
category: { type: 'string', description: 'Optional marketplace category filter' },
|
|
19
|
+
min_price_mist: { type: 'string', description: 'Optional minimum escrow budget in MIST' },
|
|
20
|
+
max_price_mist: { type: 'string', description: 'Optional maximum escrow budget in MIST' },
|
|
21
|
+
limit: { type: 'number', description: 'Maximum number of open tasks to return (default 20)' },
|
|
22
|
+
},
|
|
23
|
+
required: [],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function runMeshMarketplaceBrowse(
|
|
28
|
+
params: MeshMarketplaceBrowseParams,
|
|
29
|
+
context: MeshToolContext,
|
|
30
|
+
): Promise<Record<string, unknown>> {
|
|
31
|
+
const client = context.marketplaceClient ?? new MarketplaceClient(context.suiClient, context.networkConfig);
|
|
32
|
+
const tasks = await client.browseOpenTasks({
|
|
33
|
+
category: params.category,
|
|
34
|
+
minPriceMist: parseOptionalMist(params.min_price_mist),
|
|
35
|
+
maxPriceMist: parseOptionalMist(params.max_price_mist),
|
|
36
|
+
limit: normalizeLimit(params.limit),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
tasks,
|
|
41
|
+
count: tasks.length,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseOptionalMist(value: string | number | undefined): bigint | undefined {
|
|
46
|
+
if (value == null) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
|
|
50
|
+
return BigInt(value);
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === 'string' && /^\d+$/.test(value.trim())) {
|
|
53
|
+
return BigInt(value.trim());
|
|
54
|
+
}
|
|
55
|
+
throw new Error('Price filters must be non-negative integer strings.');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeLimit(limit?: number): number {
|
|
59
|
+
if (limit == null) {
|
|
60
|
+
return 20;
|
|
61
|
+
}
|
|
62
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) {
|
|
63
|
+
throw new Error('limit must be a positive integer.');
|
|
64
|
+
}
|
|
65
|
+
return limit;
|
|
66
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { MarketplaceClient } from '@hivemind-os/collective-core';
|
|
2
|
+
|
|
3
|
+
import type { MeshToolContext } from '../context.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_DISPUTE_WINDOW_MS = 60_000;
|
|
6
|
+
const DEFAULT_EXPIRY_HOURS = 24;
|
|
7
|
+
|
|
8
|
+
export interface MeshMarketplacePostParams {
|
|
9
|
+
capability: string;
|
|
10
|
+
category: string;
|
|
11
|
+
price_mist: string | number;
|
|
12
|
+
input?: string;
|
|
13
|
+
input_blob_id?: string;
|
|
14
|
+
agreement_hash?: string;
|
|
15
|
+
dispute_window_ms?: number;
|
|
16
|
+
expiry_hours?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const meshMarketplacePostTool = {
|
|
20
|
+
name: 'collective_marketplace_post',
|
|
21
|
+
description: 'Post an open marketplace task with category metadata',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object' as const,
|
|
24
|
+
properties: {
|
|
25
|
+
capability: { type: 'string', description: 'Capability requested for the task' },
|
|
26
|
+
category: { type: 'string', description: 'Marketplace category used for browsing' },
|
|
27
|
+
price_mist: { type: 'string', description: 'Escrowed task budget in MIST' },
|
|
28
|
+
input: { type: 'string', description: 'Inline task input to upload to blob storage' },
|
|
29
|
+
input_blob_id: { type: 'string', description: 'Existing blob id containing task input' },
|
|
30
|
+
agreement_hash: { type: 'string', description: 'Optional agreement hash or plaintext summary' },
|
|
31
|
+
dispute_window_ms: { type: 'number', description: 'Optional dispute window override in milliseconds' },
|
|
32
|
+
expiry_hours: { type: 'number', description: 'Optional task expiry in hours' },
|
|
33
|
+
},
|
|
34
|
+
required: ['capability', 'category', 'price_mist'],
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export async function runMeshMarketplacePost(
|
|
39
|
+
params: MeshMarketplacePostParams,
|
|
40
|
+
context: MeshToolContext,
|
|
41
|
+
): Promise<Record<string, unknown>> {
|
|
42
|
+
const client = context.marketplaceClient ?? new MarketplaceClient(context.suiClient, context.networkConfig);
|
|
43
|
+
const inputBlobId = await resolveInputBlobId(params, context);
|
|
44
|
+
const result = await client.postOpenTask({
|
|
45
|
+
capability: params.capability,
|
|
46
|
+
category: params.category,
|
|
47
|
+
inputBlobId,
|
|
48
|
+
agreementHash: params.agreement_hash,
|
|
49
|
+
priceMist: parseMist(params.price_mist, 'price_mist'),
|
|
50
|
+
disputeWindowMs: normalizeNonNegativeInteger(params.dispute_window_ms, DEFAULT_DISPUTE_WINDOW_MS, 'dispute_window_ms'),
|
|
51
|
+
expiryHours: normalizeNonNegativeInteger(params.expiry_hours, DEFAULT_EXPIRY_HOURS, 'expiry_hours'),
|
|
52
|
+
signer: context.keypair,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
task_id: result.taskId,
|
|
57
|
+
tx_digest: result.txDigest,
|
|
58
|
+
capability: params.capability,
|
|
59
|
+
category: params.category,
|
|
60
|
+
input_blob_id: inputBlobId,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function resolveInputBlobId(params: MeshMarketplacePostParams, context: MeshToolContext): Promise<string> {
|
|
65
|
+
if ((params.input_blob_id ? 1 : 0) + (params.input ? 1 : 0) !== 1) {
|
|
66
|
+
throw new Error('Provide exactly one of input or input_blob_id when posting a marketplace task');
|
|
67
|
+
}
|
|
68
|
+
if (params.input_blob_id) {
|
|
69
|
+
return params.input_blob_id;
|
|
70
|
+
}
|
|
71
|
+
if (!params.input) {
|
|
72
|
+
throw new Error('input or input_blob_id is required when posting a marketplace task');
|
|
73
|
+
}
|
|
74
|
+
const stored = await context.blobStore.store(new TextEncoder().encode(params.input));
|
|
75
|
+
return stored.blobId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseMist(value: string | number, field: string): bigint {
|
|
79
|
+
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
|
|
80
|
+
return BigInt(value);
|
|
81
|
+
}
|
|
82
|
+
if (typeof value === 'string' && /^\d+$/.test(value.trim())) {
|
|
83
|
+
return BigInt(value.trim());
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`${field} must be a non-negative integer string.`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeNonNegativeInteger(value: number | undefined, fallback: number, field: string): number {
|
|
89
|
+
if (value == null) {
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
92
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
93
|
+
throw new Error(`${field} must be a non-negative integer.`);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|