-
-
Notifications
You must be signed in to change notification settings - Fork 21
Expand file tree
/
Copy pathagenc-get-task.js
More file actions
188 lines (178 loc) · 5.66 KB
/
Copy pathagenc-get-task.js
File metadata and controls
188 lines (178 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// `agenc_get_task` — paid MCP tool that fetches the current state and lifecycle
// timeline of a single AgenC task. Useful for any agent that wants to decide
// whether to bid/claim, or for monitoring an in-flight job.
//
// Pricing: $0.001 USDC, settled `exact` in USDC on Solana mainnet.
import { z } from 'zod';
import { PublicKey } from '@solana/web3.js';
import { createHash } from 'node:crypto';
import {
deriveTaskPda,
getTask,
getTaskLifecycleSummary,
} from '@tetsuo-ai/sdk';
import { paid, toolError } from '../payments.js';
import { jsonSchemaFromZod } from './_shared.js';
import {
getAgenCClient,
parsePubkey,
serializeBigInts,
taskStateLabel,
} from './agenc-client.js';
const TOOL_NAME = 'agenc_get_task';
const TOOL_DESCRIPTION =
'Fetch the on-chain state and lifecycle timeline of a single AgenC task. Pass either taskPda (the derived account address) OR the {creator, taskId} pair, where taskId may be a 64-char hex string or any UTF-8 label (hashed to 32 bytes). Returns state, reward, deadline, worker counts, lifecycle events, and reward mint. AgenC = agenc.tech (Tetsuo Corp). Paid: $0.001 USDC.';
function resolveTaskId(input) {
const s = String(input).trim();
if (s.startsWith('0x') || s.startsWith('0X')) {
const hex = s.slice(2);
if (hex.length !== 64) throw new Error('hex taskId must be 32 bytes');
return Uint8Array.from(Buffer.from(hex, 'hex'));
}
if (/^[0-9a-fA-F]{64}$/.test(s)) {
return Uint8Array.from(Buffer.from(s, 'hex'));
}
return Uint8Array.from(createHash('sha256').update(s, 'utf8').digest());
}
// Single source of truth: Zod shape carries descriptions + bounds + cluster
// enum; JSON Schema derived. (No required fields — the handler enforces
// "taskPda OR {creator, taskId}".)
const inputZodShape = {
taskPda: z
.string()
.min(32)
.max(44)
.describe('Base58 task account PDA. If omitted, supply creator + taskId.')
.optional(),
creator: z
.string()
.min(32)
.max(44)
.describe('Base58 task creator wallet (required if taskPda is omitted).')
.optional(),
taskId: z
.string()
.min(1)
.max(256)
.describe('32-byte task id as 64-char hex, "0x"-prefixed hex, or any UTF-8 label (hashed via SHA-256).')
.optional(),
cluster: z.enum(['mainnet', 'devnet']).describe('Solana cluster. Defaults to mainnet.').optional(),
includeLifecycle: z
.boolean()
.describe('When true (default), include the lifecycle event timeline. Set false for a cheaper read.')
.optional(),
};
const inputJsonSchema = jsonSchemaFromZod(inputZodShape);
export async function buildAgenCGetTaskTool() {
const handler = await paid(
{
toolName: TOOL_NAME,
description: TOOL_DESCRIPTION,
scheme: 'exact',
priceUsd: '$0.001',
inputSchema: inputJsonSchema,
example: { taskPda: '8xQ…', cluster: 'devnet' },
outputExample: {
ok: true,
cluster: 'devnet',
taskPda: '8xQ…',
task: {
taskId: 'a1b2…',
state: 'Claimed',
rewardAmount: '50000000',
currentWorkers: 1,
maxWorkers: 1,
},
lifecycle: {
timeline: [{ eventName: 'created', timestamp: 1716000000 }],
},
},
},
async ({ taskPda, creator, taskId, cluster, includeLifecycle }) => {
const client = getAgenCClient(cluster);
let pda;
if (taskPda) {
pda = parsePubkey(taskPda, 'taskPda');
} else {
if (!creator || !taskId) {
return toolError('missing_input', 'Provide either taskPda OR both creator and taskId.');
}
pda = deriveTaskPda(
parsePubkey(creator, 'creator'),
resolveTaskId(taskId),
client.programId,
);
}
const task = await getTask(client.program, pda);
if (!task) {
return serializeBigInts({
ok: false,
error: 'not_found',
cluster: client.cluster,
programId: client.programId.toBase58(),
taskPda: pda.toBase58(),
message: 'no task account at that PDA on this cluster',
});
}
const taskOut = {
taskId: Buffer.from(task.taskId).toString('hex'),
state: taskStateLabel(task.state),
stateRaw: typeof task.state === 'number' ? task.state : null,
creator: task.creator.toBase58(),
rewardAmount: task.rewardAmount.toString(),
rewardMint: task.rewardMint ? task.rewardMint.toBase58() : null,
deadline: task.deadline,
currentWorkers: task.currentWorkers,
maxWorkers: task.maxWorkers,
completedAt: task.completedAt,
constraintHash: task.constraintHash
? Buffer.from(task.constraintHash).toString('hex')
: null,
private: task.constraintHash != null,
};
let lifecycleOut = null;
if (includeLifecycle !== false) {
const lifecycle = await getTaskLifecycleSummary(client.program, pda);
if (lifecycle) {
lifecycleOut = {
currentState: taskStateLabel(lifecycle.currentState),
createdAt: lifecycle.createdAt,
currentWorkers: lifecycle.currentWorkers,
maxWorkers: lifecycle.maxWorkers,
timeline: lifecycle.timeline.map((e) => ({
eventName: e.eventName,
timestamp: e.timestamp,
txSignature: e.txSignature ?? null,
actor: e.actor ? e.actor.toBase58() : null,
})),
};
}
}
return serializeBigInts({
ok: true,
cluster: client.cluster,
rpcUrl: client.rpcUrl,
programId: client.programId.toBase58(),
taskPda: pda.toBase58(),
task: taskOut,
lifecycle: lifecycleOut,
fetchedAt: new Date().toISOString(),
});
},
);
return {
name: TOOL_NAME,
title: 'AgenC get task ($0.001)',
description: TOOL_DESCRIPTION,
inputSchema: inputZodShape,
// Read-only on-chain lookup — task status moves through its lifecycle
// between calls, so not idempotent.
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
},
handler,
};
}