@hashgraphonline/standards-agent-kit 0.2.122 → 0.2.124

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 (35) hide show
  1. package/dist/cjs/standards-agent-kit.cjs +1 -1
  2. package/dist/cjs/standards-agent-kit.cjs.map +1 -1
  3. package/dist/cjs/tools/inscriber/InscribeFromBufferTool.d.ts +6 -0
  4. package/dist/cjs/tools/inscriber/InscribeFromFileTool.d.ts +3 -0
  5. package/dist/cjs/tools/inscriber/InscribeFromUrlTool.d.ts +6 -0
  6. package/dist/cjs/tools/inscriber/InscribeHashinalTool.d.ts +6 -0
  7. package/dist/cjs/tools/inscriber/base-inscriber-tools.d.ts +15 -0
  8. package/dist/es/standards-agent-kit.es33.js +44 -0
  9. package/dist/es/standards-agent-kit.es33.js.map +1 -1
  10. package/dist/es/standards-agent-kit.es34.js +34 -6
  11. package/dist/es/standards-agent-kit.es34.js.map +1 -1
  12. package/dist/es/standards-agent-kit.es35.js +65 -14
  13. package/dist/es/standards-agent-kit.es35.js.map +1 -1
  14. package/dist/es/standards-agent-kit.es36.js +42 -6
  15. package/dist/es/standards-agent-kit.es36.js.map +1 -1
  16. package/dist/es/standards-agent-kit.es37.js +38 -6
  17. package/dist/es/standards-agent-kit.es37.js.map +1 -1
  18. package/dist/es/tools/inscriber/InscribeFromBufferTool.d.ts +6 -0
  19. package/dist/es/tools/inscriber/InscribeFromFileTool.d.ts +3 -0
  20. package/dist/es/tools/inscriber/InscribeFromUrlTool.d.ts +6 -0
  21. package/dist/es/tools/inscriber/InscribeHashinalTool.d.ts +6 -0
  22. package/dist/es/tools/inscriber/base-inscriber-tools.d.ts +15 -0
  23. package/dist/umd/standards-agent-kit.umd.js +1 -1
  24. package/dist/umd/standards-agent-kit.umd.js.map +1 -1
  25. package/dist/umd/tools/inscriber/InscribeFromBufferTool.d.ts +6 -0
  26. package/dist/umd/tools/inscriber/InscribeFromFileTool.d.ts +3 -0
  27. package/dist/umd/tools/inscriber/InscribeFromUrlTool.d.ts +6 -0
  28. package/dist/umd/tools/inscriber/InscribeHashinalTool.d.ts +6 -0
  29. package/dist/umd/tools/inscriber/base-inscriber-tools.d.ts +15 -0
  30. package/package.json +3 -2
  31. package/src/tools/inscriber/InscribeFromBufferTool.ts +49 -9
  32. package/src/tools/inscriber/InscribeFromFileTool.ts +88 -15
  33. package/src/tools/inscriber/InscribeFromUrlTool.ts +40 -11
  34. package/src/tools/inscriber/InscribeHashinalTool.ts +43 -6
  35. package/src/tools/inscriber/base-inscriber-tools.ts +87 -0
@@ -12,7 +12,9 @@ declare const inscribeFromBufferSchema: z.ZodObject<{
12
12
  waitForConfirmation: z.ZodOptional<z.ZodBoolean>;
13
13
  timeoutMs: z.ZodOptional<z.ZodNumber>;
14
14
  apiKey: z.ZodOptional<z.ZodString>;
15
+ quoteOnly: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
15
16
  }, "strip", z.ZodTypeAny, {
17
+ quoteOnly: boolean;
16
18
  fileName: string;
17
19
  base64Data: string;
18
20
  tags?: string[] | undefined;
@@ -34,6 +36,7 @@ declare const inscribeFromBufferSchema: z.ZodObject<{
34
36
  waitForConfirmation?: boolean | undefined;
35
37
  timeoutMs?: number | undefined;
36
38
  apiKey?: string | undefined;
39
+ quoteOnly?: boolean | undefined;
37
40
  }>;
38
41
  export declare class InscribeFromBufferTool extends BaseInscriberQueryTool<typeof inscribeFromBufferSchema> {
39
42
  name: string;
@@ -50,7 +53,9 @@ export declare class InscribeFromBufferTool extends BaseInscriberQueryTool<typeo
50
53
  waitForConfirmation: z.ZodOptional<z.ZodBoolean>;
51
54
  timeoutMs: z.ZodOptional<z.ZodNumber>;
52
55
  apiKey: z.ZodOptional<z.ZodString>;
56
+ quoteOnly: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
53
57
  }, "strip", z.ZodTypeAny, {
58
+ quoteOnly: boolean;
54
59
  fileName: string;
55
60
  base64Data: string;
56
61
  tags?: string[] | undefined;
@@ -72,6 +77,7 @@ export declare class InscribeFromBufferTool extends BaseInscriberQueryTool<typeo
72
77
  waitForConfirmation?: boolean | undefined;
73
78
  timeoutMs?: number | undefined;
74
79
  apiKey?: string | undefined;
80
+ quoteOnly?: boolean | undefined;
75
81
  }>;
76
82
  protected executeQuery(params: z.infer<typeof inscribeFromBufferSchema>, _runManager?: CallbackManagerForToolRun): Promise<unknown>;
77
83
  private validateInput;
@@ -13,7 +13,9 @@ declare const inscribeFromFileSchema: z.ZodObject<{
13
13
  waitForConfirmation: z.ZodOptional<z.ZodBoolean>;
14
14
  timeoutMs: z.ZodOptional<z.ZodNumber>;
15
15
  apiKey: z.ZodOptional<z.ZodString>;
16
+ quoteOnly: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
16
17
  }, "strip", z.ZodTypeAny, {
18
+ quoteOnly: boolean;
17
19
  filePath: string;
18
20
  tags?: string[] | undefined;
19
21
  metadata?: Record<string, unknown> | undefined;
@@ -31,6 +33,7 @@ declare const inscribeFromFileSchema: z.ZodObject<{
31
33
  waitForConfirmation?: boolean | undefined;
32
34
  timeoutMs?: number | undefined;
33
35
  apiKey?: string | undefined;
36
+ quoteOnly?: boolean | undefined;
34
37
  }>;
35
38
  /**
36
39
  * Tool for inscribing content from file
@@ -13,8 +13,10 @@ declare const inscribeFromUrlSchema: z.ZodObject<{
13
13
  waitForConfirmation: z.ZodOptional<z.ZodBoolean>;
14
14
  timeoutMs: z.ZodOptional<z.ZodNumber>;
15
15
  apiKey: z.ZodOptional<z.ZodString>;
16
+ quoteOnly: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
16
17
  }, "strip", z.ZodTypeAny, {
17
18
  url: string;
19
+ quoteOnly: boolean;
18
20
  tags?: string[] | undefined;
19
21
  metadata?: Record<string, unknown> | undefined;
20
22
  mode?: "file" | "hashinal" | undefined;
@@ -31,6 +33,7 @@ declare const inscribeFromUrlSchema: z.ZodObject<{
31
33
  waitForConfirmation?: boolean | undefined;
32
34
  timeoutMs?: number | undefined;
33
35
  apiKey?: string | undefined;
36
+ quoteOnly?: boolean | undefined;
34
37
  }>;
35
38
  /**
36
39
  * Tool for inscribing content from URL
@@ -47,8 +50,10 @@ export declare class InscribeFromUrlTool extends BaseInscriberQueryTool<typeof i
47
50
  waitForConfirmation: z.ZodOptional<z.ZodBoolean>;
48
51
  timeoutMs: z.ZodOptional<z.ZodNumber>;
49
52
  apiKey: z.ZodOptional<z.ZodString>;
53
+ quoteOnly: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
50
54
  }, "strip", z.ZodTypeAny, {
51
55
  url: string;
56
+ quoteOnly: boolean;
52
57
  tags?: string[] | undefined;
53
58
  metadata?: Record<string, unknown> | undefined;
54
59
  mode?: "file" | "hashinal" | undefined;
@@ -65,6 +70,7 @@ export declare class InscribeFromUrlTool extends BaseInscriberQueryTool<typeof i
65
70
  waitForConfirmation?: boolean | undefined;
66
71
  timeoutMs?: number | undefined;
67
72
  apiKey?: string | undefined;
73
+ quoteOnly?: boolean | undefined;
68
74
  }>;
69
75
  protected executeQuery(params: z.infer<typeof inscribeFromUrlSchema>, _runManager?: CallbackManagerForToolRun): Promise<unknown>;
70
76
  }
@@ -27,12 +27,14 @@ declare const inscribeHashinalSchema: z.ZodObject<{
27
27
  waitForConfirmation: z.ZodOptional<z.ZodBoolean>;
28
28
  timeoutMs: z.ZodOptional<z.ZodNumber>;
29
29
  apiKey: z.ZodOptional<z.ZodString>;
30
+ quoteOnly: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
30
31
  }, "strip", z.ZodTypeAny, {
31
32
  url: string;
32
33
  type: string;
33
34
  name: string;
34
35
  description: string;
35
36
  creator: string;
37
+ quoteOnly: boolean;
36
38
  tags?: string[] | undefined;
37
39
  properties?: Record<string, unknown> | undefined;
38
40
  chunkSize?: number | undefined;
@@ -56,6 +58,7 @@ declare const inscribeHashinalSchema: z.ZodObject<{
56
58
  waitForConfirmation?: boolean | undefined;
57
59
  timeoutMs?: number | undefined;
58
60
  apiKey?: string | undefined;
61
+ quoteOnly?: boolean | undefined;
59
62
  attributes?: {
60
63
  value: string | number;
61
64
  trait_type: string;
@@ -91,12 +94,14 @@ export declare class InscribeHashinalTool extends BaseInscriberQueryTool<typeof
91
94
  waitForConfirmation: z.ZodOptional<z.ZodBoolean>;
92
95
  timeoutMs: z.ZodOptional<z.ZodNumber>;
93
96
  apiKey: z.ZodOptional<z.ZodString>;
97
+ quoteOnly: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
94
98
  }, "strip", z.ZodTypeAny, {
95
99
  url: string;
96
100
  type: string;
97
101
  name: string;
98
102
  description: string;
99
103
  creator: string;
104
+ quoteOnly: boolean;
100
105
  tags?: string[] | undefined;
101
106
  properties?: Record<string, unknown> | undefined;
102
107
  chunkSize?: number | undefined;
@@ -120,6 +125,7 @@ export declare class InscribeHashinalTool extends BaseInscriberQueryTool<typeof
120
125
  waitForConfirmation?: boolean | undefined;
121
126
  timeoutMs?: number | undefined;
122
127
  apiKey?: string | undefined;
128
+ quoteOnly?: boolean | undefined;
123
129
  attributes?: {
124
130
  value: string | number;
125
131
  trait_type: string;
@@ -2,6 +2,7 @@ import { BaseHederaTransactionTool, BaseHederaQueryTool, BaseServiceBuilder } fr
2
2
  import { InscriberBuilder } from '../../builders/inscriber/inscriber-builder';
3
3
  import { InscriberTransactionToolParams, InscriberQueryToolParams } from './inscriber-tool-params';
4
4
  import { ContentResolverInterface } from '../../types/content-resolver';
5
+ import { InscriptionInput, InscriptionOptions, QuoteResult } from '@hashgraphonline/standards-sdk';
5
6
  import { z } from 'zod';
6
7
  /**
7
8
  * Base class for Inscriber transaction tools
@@ -19,6 +20,13 @@ export declare abstract class BaseInscriberTransactionTool<T extends z.ZodObject
19
20
  * Get content resolver with fallback to registry
20
21
  */
21
22
  protected getContentResolver(): ContentResolverInterface | null;
23
+ /**
24
+ * Generate a quote for an inscription without executing it
25
+ * @param input - The inscription input data
26
+ * @param options - Inscription options
27
+ * @returns Promise containing the quote result
28
+ */
29
+ protected generateInscriptionQuote(input: InscriptionInput, options: InscriptionOptions): Promise<QuoteResult>;
22
30
  }
23
31
  /**
24
32
  * Base class for Inscriber query tools
@@ -36,4 +44,11 @@ export declare abstract class BaseInscriberQueryTool<T extends z.ZodObject<z.Zod
36
44
  * Get content resolver with fallback to registry
37
45
  */
38
46
  protected getContentResolver(): ContentResolverInterface | null;
47
+ /**
48
+ * Generate a quote for an inscription without executing it
49
+ * @param input - The inscription input data
50
+ * @param options - Inscription options
51
+ * @returns Promise containing the quote result
52
+ */
53
+ protected generateInscriptionQuote(input: InscriptionInput, options: InscriptionOptions): Promise<QuoteResult>;
39
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hashgraphonline/standards-agent-kit",
3
- "version": "0.2.122",
3
+ "version": "0.2.124",
4
4
  "description": "A modular SDK for building on-chain autonomous agents using Hashgraph Online Standards, including HCS-10 for agent discovery and communication.",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/standards-agent-kit.cjs",
@@ -43,6 +43,7 @@
43
43
  "demo:plugin:weather": "tsx examples/plugins/weather/index.ts",
44
44
  "demo:plugin:defi": "tsx examples/plugins/defi/index.ts",
45
45
  "demo:plugin:openconvai": "tsx examples/openconvai-plugin-example.ts",
46
+ "demo:inscription-quotes": "tsx examples/inscription-quote-demo.ts",
46
47
  "standards-agent:start": "tsx examples/standards-expert/cli.ts -- start",
47
48
  "standards-agent:process-docs": "tsx examples/standards-expert/cli.ts -- process-docs --all-repos",
48
49
  "typecheck": "tsc --noEmit"
@@ -89,7 +90,7 @@
89
90
  },
90
91
  "dependencies": {
91
92
  "@hashgraph/sdk": "^2.69.0",
92
- "@hashgraphonline/standards-sdk": "0.0.170",
93
+ "@hashgraphonline/standards-sdk": "^0.0.180",
93
94
  "@langchain/community": "^0.3.49",
94
95
  "@langchain/core": "^0.3.66",
95
96
  "@langchain/openai": "^0.6.3",
@@ -47,6 +47,11 @@ const inscribeFromBufferSchema = z.object({
47
47
  'Timeout in milliseconds for inscription (default: no timeout - waits until completion)'
48
48
  ),
49
49
  apiKey: z.string().optional().describe('API key for inscription service'),
50
+ quoteOnly: z
51
+ .boolean()
52
+ .optional()
53
+ .default(false)
54
+ .describe('If true, returns a cost quote instead of executing the inscription'),
50
55
  });
51
56
 
52
57
  export class InscribeFromBufferTool extends BaseInscriberQueryTool<
@@ -54,7 +59,7 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
54
59
  > {
55
60
  name = 'inscribeFromBuffer';
56
61
  description =
57
- 'Inscribe content that you have already retrieved or displayed. When user says "inscribe it" after you showed search results or other content, use THIS tool. The base64Data field accepts PLAIN TEXT (not just base64) and content reference IDs in format "content-ref:[id]". Pass the EXACT content from your previous response or MCP tool output. DO NOT generate new content or create repetitive text. Content references are automatically resolved to the original content for inscription.';
62
+ 'Inscribe content that you have already retrieved or displayed. When user says "inscribe it" after you showed search results or other content, use THIS tool. The base64Data field accepts PLAIN TEXT (not just base64) and content reference IDs in format "content-ref:[id]". Pass the EXACT content from your previous response or MCP tool output. DO NOT generate new content or create repetitive text. Content references are automatically resolved to the original content for inscription. Set quoteOnly=true to get cost estimates without executing the inscription.';
58
63
 
59
64
  private config = loadConfig();
60
65
 
@@ -85,7 +90,7 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
85
90
  metadata: params.metadata,
86
91
  tags: params.tags,
87
92
  chunkSize: params.chunkSize,
88
- waitForConfirmation: params.waitForConfirmation ?? true,
93
+ waitForConfirmation: params.quoteOnly ? false : (params.waitForConfirmation ?? true),
89
94
  waitMaxAttempts: 10,
90
95
  waitIntervalMs: 3000,
91
96
  apiKey: params.apiKey,
@@ -94,8 +99,42 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
94
99
  .includes('mainnet')
95
100
  ? 'mainnet'
96
101
  : 'testnet',
102
+ quoteOnly: params.quoteOnly,
97
103
  };
98
104
 
105
+ if (params.quoteOnly) {
106
+ try {
107
+ const quote = await this.generateInscriptionQuote(
108
+ {
109
+ type: 'buffer',
110
+ buffer,
111
+ fileName: resolvedFileName,
112
+ mimeType: resolvedMimeType,
113
+ },
114
+ options
115
+ );
116
+
117
+ return {
118
+ success: true,
119
+ quote: {
120
+ totalCostHbar: quote.totalCostHbar,
121
+ validUntil: quote.validUntil,
122
+ breakdown: quote.breakdown,
123
+ },
124
+ contentInfo: {
125
+ fileName: resolvedFileName,
126
+ mimeType: resolvedMimeType,
127
+ sizeBytes: buffer.length,
128
+ },
129
+ message: `Quote generated for buffer content: ${resolvedFileName} (${(buffer.length / 1024).toFixed(2)} KB)\nTotal cost: ${quote.totalCostHbar} HBAR`,
130
+ };
131
+ } catch (error) {
132
+ const errorMessage =
133
+ error instanceof Error ? error.message : 'Failed to generate inscription quote';
134
+ throw new Error(`Quote generation failed: ${errorMessage}`);
135
+ }
136
+ }
137
+
99
138
  const timeoutMs =
100
139
  params.timeoutMs || (options.waitForConfirmation ? 60000 : undefined);
101
140
 
@@ -210,20 +249,24 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
210
249
  result: Awaited<ReturnType<typeof this.inscriberBuilder.inscribe>>,
211
250
  options: InscriptionOptions
212
251
  ): string {
213
- if (result.confirmed) {
214
- const topicId = result.inscription?.topic_id || result.result.topicId;
252
+ if (result.confirmed && !result.quote) {
253
+ const topicId = result.inscription?.topic_id || (result.result as any).topicId;
215
254
  const network = options.network || 'testnet';
216
255
  const cdnUrl = topicId
217
256
  ? `https://kiloscribe.com/api/inscription-cdn/${topicId}?network=${network}`
218
257
  : null;
219
258
  return `Successfully inscribed and confirmed content on the Hedera network!\n\nTransaction ID: ${
220
- result.result.transactionId
259
+ (result.result as any).transactionId
221
260
  }\nTopic ID: ${topicId || 'N/A'}${
222
261
  cdnUrl ? `\nView inscription: ${cdnUrl}` : ''
223
262
  }\n\nThe inscription is now available.`;
224
263
  }
225
264
 
226
- return `Successfully submitted inscription to the Hedera network!\n\nTransaction ID: ${result.result.transactionId}\n\nThe inscription is processing and will be confirmed shortly.`;
265
+ if (!result.quote && !result.confirmed) {
266
+ return `Successfully submitted inscription to the Hedera network!\n\nTransaction ID: ${(result.result as any).transactionId}\n\nThe inscription is processing and will be confirmed shortly.`;
267
+ }
268
+
269
+ return 'Inscription operation completed.';
227
270
  }
228
271
 
229
272
  private async resolveContent(
@@ -238,11 +281,9 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
238
281
  }> {
239
282
  const trimmedInput = input.trim();
240
283
 
241
- // Try to get resolver from either injected dependency or registry
242
284
  const resolver = this.getContentResolver() || ContentResolverRegistry.getResolver();
243
285
 
244
286
  if (!resolver) {
245
- // No resolver available, handle content directly
246
287
  return this.handleDirectContent(trimmedInput, providedMimeType, providedFileName);
247
288
  }
248
289
 
@@ -267,7 +308,6 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
267
308
  }
268
309
  }
269
310
 
270
- // No reference found, handle as direct content
271
311
  return this.handleDirectContent(trimmedInput, providedMimeType, providedFileName);
272
312
  }
273
313
 
@@ -9,7 +9,12 @@ import * as path from 'path';
9
9
  * Schema for inscribing from file
10
10
  */
11
11
  const inscribeFromFileSchema = z.object({
12
- filePath: z.string().min(1, 'File path cannot be empty').describe('The file path of the content to inscribe. Must point to a valid, non-empty file.'),
12
+ filePath: z
13
+ .string()
14
+ .min(1, 'File path cannot be empty')
15
+ .describe(
16
+ 'The file path of the content to inscribe. Must point to a valid, non-empty file.'
17
+ ),
13
18
  mode: z
14
19
  .enum(['file', 'hashinal'])
15
20
  .optional()
@@ -39,6 +44,13 @@ const inscribeFromFileSchema = z.object({
39
44
  .optional()
40
45
  .describe('Timeout in milliseconds for inscription (default: no timeout)'),
41
46
  apiKey: z.string().optional().describe('API key for inscription service'),
47
+ quoteOnly: z
48
+ .boolean()
49
+ .optional()
50
+ .default(false)
51
+ .describe(
52
+ 'If true, returns a cost quote instead of executing the inscription'
53
+ ),
42
54
  });
43
55
 
44
56
  /**
@@ -49,7 +61,7 @@ export class InscribeFromFileTool extends BaseInscriberQueryTool<
49
61
  > {
50
62
  name = 'inscribeFromFile';
51
63
  description =
52
- 'Inscribe content from a local file to the Hedera network using a file path. IMPORTANT: Only use this tool when you have a valid file path to actual content. The file must exist and contain meaningful data (minimum 10 bytes). For files accessed through MCP filesystem tools, consider reading the file content first and using inscribeFromBuffer instead.';
64
+ 'Inscribe content from a local file to the Hedera network using a file path. IMPORTANT: Only use this tool when you have a valid file path to actual content. The file must exist and contain meaningful data (minimum 10 bytes). For files accessed through MCP filesystem tools, consider reading the file content first and using inscribeFromBuffer instead. Set quoteOnly=true to get cost estimates without executing the inscription.';
53
65
 
54
66
  get specificInputSchema(): typeof inscribeFromFileSchema {
55
67
  return inscribeFromFileSchema;
@@ -59,9 +71,10 @@ export class InscribeFromFileTool extends BaseInscriberQueryTool<
59
71
  params: z.infer<typeof inscribeFromFileSchema>,
60
72
  _runManager?: CallbackManagerForToolRun
61
73
  ): Promise<unknown> {
62
- console.log(`[DEBUG] InscribeFromFileTool.executeQuery called with: ${params.filePath}`);
63
-
64
- // File validation
74
+ console.log(
75
+ `[DEBUG] InscribeFromFileTool.executeQuery called with: ${params.filePath}`
76
+ );
77
+
65
78
  let fileContent: Buffer;
66
79
  try {
67
80
  console.log(`[DEBUG] Checking file: ${params.filePath}`);
@@ -87,7 +100,12 @@ export class InscribeFromFileTool extends BaseInscriberQueryTool<
87
100
  }
88
101
 
89
102
  if (stats.size > 100 * 1024 * 1024) {
90
- console.log(`[InscribeFromFileTool] WARNING: Large file detected (${(stats.size / (1024 * 1024)).toFixed(2)} MB)`);
103
+ console.log(
104
+ `[InscribeFromFileTool] WARNING: Large file detected (${(
105
+ stats.size /
106
+ (1024 * 1024)
107
+ ).toFixed(2)} MB)`
108
+ );
91
109
  }
92
110
 
93
111
  this.logger?.info('Reading file content...');
@@ -109,7 +127,11 @@ export class InscribeFromFileTool extends BaseInscriberQueryTool<
109
127
  const fileName = path.basename(params.filePath);
110
128
  const mimeType = this.getMimeType(fileName);
111
129
  if (mimeType.startsWith('text/') || mimeType === 'application/json') {
112
- const textContent = fileContent.toString('utf8', 0, Math.min(fileContent.length, 1000));
130
+ const textContent = fileContent.toString(
131
+ 'utf8',
132
+ 0,
133
+ Math.min(fileContent.length, 1000)
134
+ );
113
135
  if (textContent.trim() === '') {
114
136
  throw new Error(
115
137
  `File "${params.filePath}" contains only whitespace or empty content. Cannot inscribe meaningless data.`
@@ -138,7 +160,9 @@ export class InscribeFromFileTool extends BaseInscriberQueryTool<
138
160
  metadata: params.metadata,
139
161
  tags: params.tags,
140
162
  chunkSize: params.chunkSize,
141
- waitForConfirmation: params.waitForConfirmation ?? true,
163
+ waitForConfirmation: params.quoteOnly
164
+ ? false
165
+ : params.waitForConfirmation ?? true,
142
166
  waitMaxAttempts: 10,
143
167
  waitIntervalMs: 3000,
144
168
  apiKey: params.apiKey,
@@ -147,15 +171,59 @@ export class InscribeFromFileTool extends BaseInscriberQueryTool<
147
171
  .includes('mainnet')
148
172
  ? 'mainnet'
149
173
  : 'testnet',
174
+ quoteOnly: params.quoteOnly,
150
175
  };
151
176
 
177
+ if (params.quoteOnly) {
178
+ try {
179
+ const quote = await this.generateInscriptionQuote(
180
+ {
181
+ type: 'buffer',
182
+ buffer: Buffer.from(base64Data, 'base64'),
183
+ fileName,
184
+ mimeType,
185
+ },
186
+ options
187
+ );
188
+
189
+ return {
190
+ success: true,
191
+ quote: {
192
+ totalCostHbar: quote.totalCostHbar,
193
+ validUntil: quote.validUntil,
194
+ breakdown: quote.breakdown,
195
+ },
196
+ contentInfo: {
197
+ fileName,
198
+ mimeType,
199
+ sizeBytes: fileContent.length,
200
+ filePath: params.filePath,
201
+ },
202
+ message: `Quote generated for file: ${fileName} (${(
203
+ fileContent.length / 1024
204
+ ).toFixed(2)} KB)\nTotal cost: ${
205
+ quote.totalCostHbar
206
+ } HBAR`,
207
+ };
208
+ } catch (error) {
209
+ const errorMessage =
210
+ error instanceof Error
211
+ ? error.message
212
+ : 'Failed to generate inscription quote';
213
+ throw new Error(`Quote generation failed: ${errorMessage}`);
214
+ }
215
+ }
216
+
152
217
  try {
153
- let result: any;
154
-
218
+ let result: unknown;
219
+
155
220
  if (params.timeoutMs) {
156
221
  const timeoutPromise = new Promise((_, reject) => {
157
222
  setTimeout(
158
- () => reject(new Error(`Inscription timed out after ${params.timeoutMs}ms`)),
223
+ () =>
224
+ reject(
225
+ new Error(`Inscription timed out after ${params.timeoutMs}ms`)
226
+ ),
159
227
  params.timeoutMs
160
228
  );
161
229
  });
@@ -184,19 +252,24 @@ export class InscribeFromFileTool extends BaseInscriberQueryTool<
184
252
  );
185
253
  }
186
254
 
187
- if (result.confirmed) {
188
- const topicId = result.inscription?.topic_id || result.result.topicId;
255
+ const inscriptionResult = result as any;
256
+ if (inscriptionResult.confirmed && !inscriptionResult.quote) {
257
+ const topicId =
258
+ inscriptionResult.inscription?.topic_id ||
259
+ inscriptionResult.result.topicId;
189
260
  const network = options.network || 'testnet';
190
261
  const cdnUrl = topicId
191
262
  ? `https://kiloscribe.com/api/inscription-cdn/${topicId}?network=${network}`
192
263
  : null;
193
264
  return `Successfully inscribed and confirmed content on the Hedera network!\n\nTransaction ID: ${
194
- result.result.transactionId
265
+ inscriptionResult.result.transactionId
195
266
  }\nTopic ID: ${topicId || 'N/A'}${
196
267
  cdnUrl ? `\nView inscription: ${cdnUrl}` : ''
197
268
  }\n\nThe inscription is now available.`;
269
+ } else if (!inscriptionResult.quote && !inscriptionResult.confirmed) {
270
+ return `Successfully submitted inscription to the Hedera network!\n\nTransaction ID: ${inscriptionResult.result.transactionId}\n\nThe inscription is processing and will be confirmed shortly.`;
198
271
  } else {
199
- return `Successfully submitted inscription to the Hedera network!\n\nTransaction ID: ${result.result.transactionId}\n\nThe inscription is processing and will be confirmed shortly.`;
272
+ return 'Inscription operation completed.';
200
273
  }
201
274
  } catch (error) {
202
275
  const errorMessage =
@@ -40,6 +40,11 @@ const inscribeFromUrlSchema = z.object({
40
40
  .string()
41
41
  .optional()
42
42
  .describe('API key for inscription service'),
43
+ quoteOnly: z
44
+ .boolean()
45
+ .optional()
46
+ .default(false)
47
+ .describe('If true, returns a cost quote instead of executing the inscription'),
43
48
  });
44
49
 
45
50
 
@@ -48,7 +53,7 @@ const inscribeFromUrlSchema = z.object({
48
53
  */
49
54
  export class InscribeFromUrlTool extends BaseInscriberQueryTool<typeof inscribeFromUrlSchema> {
50
55
  name = 'inscribeFromUrl';
51
- description = 'ONLY for direct FILE DOWNLOAD URLs ending with file extensions (.pdf, .jpg, .png, .json, .zip). NEVER use for web pages, articles, or ANY HTML content - it WILL FAIL. If you have already retrieved content from any source (including MCP tools), you MUST use inscribeFromBuffer instead. This tool downloads files from URLs - it does NOT inscribe content you already have. When asked to "inscribe it" after retrieving content, ALWAYS use inscribeFromBuffer with the actual content.';
56
+ description = 'ONLY for direct FILE DOWNLOAD URLs ending with file extensions (.pdf, .jpg, .png, .json, .zip). NEVER use for web pages, articles, or ANY HTML content - it WILL FAIL. If you have already retrieved content from any source (including MCP tools), you MUST use inscribeFromBuffer instead. This tool downloads files from URLs - it does NOT inscribe content you already have. When asked to "inscribe it" after retrieving content, ALWAYS use inscribeFromBuffer with the actual content. Set quoteOnly=true to get cost estimates without executing the inscription.';
52
57
 
53
58
  get specificInputSchema() {
54
59
  return inscribeFromUrlSchema;
@@ -86,7 +91,6 @@ export class InscribeFromUrlTool extends BaseInscriberQueryTool<typeof inscribeF
86
91
  const timeoutId = setTimeout(() => controller.abort(), 10000);
87
92
 
88
93
  try {
89
- // First try HEAD request
90
94
  const headResponse = await fetch(params.url, {
91
95
  method: 'HEAD',
92
96
  signal: controller.signal,
@@ -101,7 +105,6 @@ export class InscribeFromUrlTool extends BaseInscriberQueryTool<typeof inscribeF
101
105
  const contentType = headResponse.headers.get('content-type') || '';
102
106
  const contentLength = headResponse.headers.get('content-length');
103
107
 
104
- // Check if content type indicates HTML/web page
105
108
  const webPageContentTypes = [
106
109
  'text/html',
107
110
  'application/xhtml+xml',
@@ -120,7 +123,6 @@ export class InscribeFromUrlTool extends BaseInscriberQueryTool<typeof inscribeF
120
123
  throw new Error(`URL content is too small (${contentLength} bytes). Content must be at least 10 bytes.`);
121
124
  }
122
125
 
123
- // If HEAD doesn't provide content-type, do a partial GET to check
124
126
  if (!contentType || contentType === 'application/octet-stream') {
125
127
  console.log(`[InscribeFromUrlTool] Content-Type unclear, fetching first 1KB to verify...`);
126
128
 
@@ -142,7 +144,6 @@ export class InscribeFromUrlTool extends BaseInscriberQueryTool<typeof inscribeF
142
144
  const bytes = new Uint8Array(buffer);
143
145
  const text = new TextDecoder('utf-8', { fatal: false }).decode(bytes.slice(0, 512));
144
146
 
145
- // Check if it looks like HTML
146
147
  if (text.toLowerCase().includes('<!doctype html') ||
147
148
  text.toLowerCase().includes('<html') ||
148
149
  text.match(/<meta\s+[^>]*>/i) ||
@@ -155,7 +156,6 @@ export class InscribeFromUrlTool extends BaseInscriberQueryTool<typeof inscribeF
155
156
  if (getError instanceof Error && getError.message.includes('HTML content')) {
156
157
  throw getError;
157
158
  }
158
- // If partial GET fails, continue anyway
159
159
  console.log(`[InscribeFromUrlTool] Could not perform partial GET validation: ${getError instanceof Error ? getError.message : 'Unknown error'}`);
160
160
  }
161
161
  }
@@ -182,13 +182,40 @@ export class InscribeFromUrlTool extends BaseInscriberQueryTool<typeof inscribeF
182
182
  metadata: params.metadata,
183
183
  tags: params.tags,
184
184
  chunkSize: params.chunkSize,
185
- waitForConfirmation: params.waitForConfirmation ?? true,
185
+ waitForConfirmation: params.quoteOnly ? false : (params.waitForConfirmation ?? true),
186
186
  waitMaxAttempts: 10,
187
187
  waitIntervalMs: 3000,
188
188
  apiKey: params.apiKey,
189
189
  network: this.inscriberBuilder['hederaKit'].client.network.toString().includes('mainnet') ? 'mainnet' : 'testnet',
190
+ quoteOnly: params.quoteOnly,
190
191
  };
191
192
 
193
+ if (params.quoteOnly) {
194
+ try {
195
+ const quote = await this.generateInscriptionQuote(
196
+ { type: 'url', url: params.url },
197
+ options
198
+ );
199
+
200
+ return {
201
+ success: true,
202
+ quote: {
203
+ totalCostHbar: quote.totalCostHbar,
204
+ validUntil: quote.validUntil,
205
+ breakdown: quote.breakdown,
206
+ },
207
+ contentInfo: {
208
+ url: params.url,
209
+ },
210
+ message: `Quote generated for URL: ${params.url}\nTotal cost: ${quote.totalCostHbar} HBAR`,
211
+ };
212
+ } catch (error) {
213
+ const errorMessage =
214
+ error instanceof Error ? error.message : 'Failed to generate inscription quote';
215
+ throw new Error(`Quote generation failed: ${errorMessage}`);
216
+ }
217
+ }
218
+
192
219
  try {
193
220
  let result: Awaited<ReturnType<typeof this.inscriberBuilder.inscribe>>;
194
221
 
@@ -214,13 +241,15 @@ export class InscribeFromUrlTool extends BaseInscriberQueryTool<typeof inscribeF
214
241
  );
215
242
  }
216
243
 
217
- if (result.confirmed) {
218
- const topicId = result.inscription?.topic_id || result.result.topicId;
244
+ if (result.confirmed && !result.quote) {
245
+ const topicId = result.inscription?.topic_id || (result.result as any).topicId;
219
246
  const network = options.network || 'testnet';
220
247
  const cdnUrl = topicId ? `https://kiloscribe.com/api/inscription-cdn/${topicId}?network=${network}` : null;
221
- return `Successfully inscribed and confirmed content on the Hedera network!\n\nTransaction ID: ${result.result.transactionId}\nTopic ID: ${topicId || 'N/A'}${cdnUrl ? `\nView inscription: ${cdnUrl}` : ''}\n\nThe inscription is now available.`;
248
+ return `Successfully inscribed and confirmed content on the Hedera network!\n\nTransaction ID: ${(result.result as any).transactionId}\nTopic ID: ${topicId || 'N/A'}${cdnUrl ? `\nView inscription: ${cdnUrl}` : ''}\n\nThe inscription is now available.`;
249
+ } else if (!result.quote && !result.confirmed) {
250
+ return `Successfully submitted inscription to the Hedera network!\n\nTransaction ID: ${(result.result as any).transactionId}\n\nThe inscription is processing and will be confirmed shortly.`;
222
251
  } else {
223
- return `Successfully submitted inscription to the Hedera network!\n\nTransaction ID: ${result.result.transactionId}\n\nThe inscription is processing and will be confirmed shortly.`;
252
+ return 'Inscription operation completed.';
224
253
  }
225
254
  } catch (error) {
226
255
  const errorMessage = error instanceof Error ? error.message : 'Failed to inscribe from URL';