@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.
Files changed (49) hide show
  1. package/.turbo/turbo-build.log +14 -0
  2. package/dist/index.d.ts +493 -0
  3. package/dist/index.js +2129 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +31 -0
  6. package/src/context.ts +58 -0
  7. package/src/encryption.ts +25 -0
  8. package/src/index.ts +41 -0
  9. package/src/resources/agent.ts +77 -0
  10. package/src/resources/capabilities.ts +24 -0
  11. package/src/resources/index.ts +70 -0
  12. package/src/resources/task.ts +58 -0
  13. package/src/resources/wallet.ts +32 -0
  14. package/src/tools/analytics.ts +122 -0
  15. package/src/tools/balance.ts +36 -0
  16. package/src/tools/deactivate.ts +33 -0
  17. package/src/tools/discover.ts +256 -0
  18. package/src/tools/dispute.ts +135 -0
  19. package/src/tools/execute-async.ts +39 -0
  20. package/src/tools/execute.ts +418 -0
  21. package/src/tools/index.ts +152 -0
  22. package/src/tools/indexer-client.ts +163 -0
  23. package/src/tools/marketplace-accept-bid.ts +43 -0
  24. package/src/tools/marketplace-bid.ts +56 -0
  25. package/src/tools/marketplace-browse.ts +66 -0
  26. package/src/tools/marketplace-post.ts +96 -0
  27. package/src/tools/metering.ts +214 -0
  28. package/src/tools/multi-execute.ts +218 -0
  29. package/src/tools/policy-update.ts +94 -0
  30. package/src/tools/register.ts +78 -0
  31. package/src/tools/relay-registry.ts +95 -0
  32. package/src/tools/stake.ts +103 -0
  33. package/src/tools/task-history.ts +86 -0
  34. package/src/tools/task-status.ts +66 -0
  35. package/tests/analytics.test.ts +41 -0
  36. package/tests/auth-errors.test.ts +85 -0
  37. package/tests/balance.test.ts +32 -0
  38. package/tests/context.test.ts +112 -0
  39. package/tests/discover.test.ts +207 -0
  40. package/tests/dispute.test.ts +140 -0
  41. package/tests/execute.test.ts +150 -0
  42. package/tests/marketplace.test.ts +117 -0
  43. package/tests/metering.test.ts +173 -0
  44. package/tests/multi-execute.test.ts +123 -0
  45. package/tests/relay-registry.test.ts +71 -0
  46. package/tests/stake.test.ts +90 -0
  47. package/tsconfig.json +9 -0
  48. package/tsup.config.ts +10 -0
  49. 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
+ }