ff1-cli 1.0.2 → 1.0.3

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.
@@ -49,11 +49,11 @@ function loadConfig() {
49
49
  maxTokens: parseInt(process.env.MAX_TOKENS || '4000', 10),
50
50
  supportsFunctionCalling: true,
51
51
  },
52
- chatgpt: {
52
+ gpt: {
53
53
  apiKey: process.env.OPENAI_API_KEY || '',
54
54
  baseURL: 'https://api.openai.com/v1',
55
- model: 'gpt-4o',
56
- availableModels: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo'],
55
+ model: 'gpt-4.1',
56
+ availableModels: ['gpt-4.1', 'gpt-4.1-mini', 'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo'],
57
57
  timeout: 30000,
58
58
  maxRetries: 3,
59
59
  temperature: 0.3,
@@ -105,8 +105,9 @@ function loadConfig() {
105
105
  models: mergedModels,
106
106
  };
107
107
  }
108
- catch (_error) {
109
- console.warn('Warning: Failed to parse config.json, using defaults');
108
+ catch (error) {
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ console.warn(`Warning: Failed to parse config at ${configPath}. ${message}. Using defaults.`);
110
111
  return defaultConfig;
111
112
  }
112
113
  }
@@ -161,7 +162,7 @@ function getBrowserConfig() {
161
162
  * Get playlist configuration including private key for signing
162
163
  *
163
164
  * @returns {Object} Playlist configuration
164
- * @returns {string|null} returns.privateKey - Ed25519 private key in base64 format (null if not configured)
165
+ * @returns {string|null} returns.privateKey - Ed25519 private key in base64 or hex format (null if not configured)
165
166
  */
166
167
  function getPlaylistConfig() {
167
168
  const config = getConfig();
@@ -303,13 +304,12 @@ function validateConfig(modelName) {
303
304
  // Validate playlist configuration (optional, but warn if configured incorrectly)
304
305
  if (config.playlist && config.playlist.privateKey) {
305
306
  const key = config.playlist.privateKey;
306
- if (key !== 'your_ed25519_private_key_base64_here' &&
307
- typeof key === 'string' &&
308
- key.length > 0) {
309
- // Check if it looks like valid base64
307
+ const placeholderPattern = /your_ed25519_private_key/i;
308
+ if (!placeholderPattern.test(key) && typeof key === 'string' && key.length > 0) {
310
309
  const base64Regex = /^[A-Za-z0-9+/]+=*$/;
311
- if (!base64Regex.test(key)) {
312
- errors.push('playlist.privateKey must be a valid base64-encoded ed25519 private key');
310
+ const hexRegex = /^(0x)?[0-9a-fA-F]+$/;
311
+ if (!base64Regex.test(key) && !hexRegex.test(key)) {
312
+ errors.push('playlist.privateKey must be a valid base64- or hex-encoded ed25519 private key');
313
313
  }
314
314
  }
315
315
  }
@@ -48,6 +48,7 @@ const openai_1 = __importDefault(require("openai"));
48
48
  const chalk_1 = __importDefault(require("chalk"));
49
49
  const config_1 = require("../config");
50
50
  const utils_1 = require("./utils");
51
+ const logger = __importStar(require("../logger"));
51
52
  // Cache for AI clients
52
53
  const clientCache = new Map();
53
54
  /**
@@ -369,13 +370,13 @@ const intentParserFunctionSchemas = [
369
370
  type: 'function',
370
371
  function: {
371
372
  name: 'confirm_send_playlist',
372
- description: 'Confirm the playlist file path and device name for sending. This function is called after the user mentions "send" or similar phrases.',
373
+ description: 'Confirm the playlist file path or hosted URL and device name for sending. This function is called after the user mentions "send" or similar phrases.',
373
374
  parameters: {
374
375
  type: 'object',
375
376
  properties: {
376
377
  filePath: {
377
378
  type: 'string',
378
- description: 'Path to the playlist file (default: "./playlist.json")',
379
+ description: 'Path to playlist file or playlist URL (default: "./playlist.json")',
379
380
  },
380
381
  deviceName: {
381
382
  type: 'string',
@@ -483,16 +484,67 @@ function printMarkdownContent(content) {
483
484
  const lines = content.split('\n');
484
485
  for (const line of lines) {
485
486
  if (line.trim()) {
486
- console.log(formatMarkdown(line));
487
+ logger.verbose(formatMarkdown(line));
487
488
  }
488
489
  else if (line === '') {
489
- console.log();
490
+ logger.verbose();
490
491
  }
491
492
  }
492
493
  }
493
494
  function sleep(ms) {
494
495
  return new Promise((resolve) => setTimeout(resolve, ms));
495
496
  }
497
+ function extractDomains(text) {
498
+ if (!text) {
499
+ return [];
500
+ }
501
+ const matches = text.match(/[a-z0-9-]+\.(eth|tez)/gi);
502
+ return matches ? matches.map((match) => match.toLowerCase()) : [];
503
+ }
504
+ function normalizeDomainInput(address, userDomains) {
505
+ if (!address || userDomains.length === 0) {
506
+ return address;
507
+ }
508
+ const lower = address.toLowerCase();
509
+ if (!lower.endsWith('.eth') && !lower.endsWith('.tez')) {
510
+ return address;
511
+ }
512
+ const baseName = lower.replace(/\.(eth|tez)$/i, '');
513
+ const match = userDomains.find((domain) => domain.replace(/\.(eth|tez)$/i, '') === baseName);
514
+ return match || address;
515
+ }
516
+ function normalizeRequirementDomains(params, userDomains) {
517
+ if (userDomains.length === 0) {
518
+ return params;
519
+ }
520
+ const normalized = params.requirements.map((req) => {
521
+ if (req.type !== 'query_address' || typeof req.ownerAddress !== 'string') {
522
+ return req;
523
+ }
524
+ return {
525
+ ...req,
526
+ ownerAddress: normalizeDomainInput(req.ownerAddress, userDomains),
527
+ };
528
+ });
529
+ return {
530
+ ...params,
531
+ requirements: normalized,
532
+ };
533
+ }
534
+ function buildToolResponseMessages(toolCalls, responses) {
535
+ return toolCalls
536
+ .filter((toolCall) => toolCall.id)
537
+ .map((toolCall) => {
538
+ const content = toolCall.id && Object.prototype.hasOwnProperty.call(responses, toolCall.id)
539
+ ? responses[toolCall.id]
540
+ : { error: `Unknown function: ${toolCall.function.name}` };
541
+ return {
542
+ role: 'tool',
543
+ tool_call_id: toolCall.id,
544
+ content: JSON.stringify(content),
545
+ };
546
+ });
547
+ }
496
548
  async function processNonStreamingResponse(response) {
497
549
  const message = response.choices[0]?.message;
498
550
  if (!message) {
@@ -500,7 +552,7 @@ async function processNonStreamingResponse(response) {
500
552
  }
501
553
  if (message.content) {
502
554
  printMarkdownContent(message.content);
503
- console.log();
555
+ logger.verbose();
504
556
  }
505
557
  return { message };
506
558
  }
@@ -572,10 +624,10 @@ async function processStreamingResponse(stream) {
572
624
  for (const line of lines) {
573
625
  if (line.trim()) {
574
626
  const formatted = formatMarkdown(line);
575
- console.log(formatted);
627
+ logger.verbose(formatted);
576
628
  }
577
629
  else if (line === '') {
578
- console.log();
630
+ logger.verbose();
579
631
  }
580
632
  }
581
633
  printedUpTo = lastNewlineIndex + 1;
@@ -621,11 +673,11 @@ async function processStreamingResponse(stream) {
621
673
  const remainingText = contentBuffer.substring(printedUpTo);
622
674
  if (remainingText.trim()) {
623
675
  const formatted = formatMarkdown(remainingText);
624
- console.log(formatted);
676
+ logger.verbose(formatted);
625
677
  }
626
678
  }
627
679
  if (contentBuffer.length > 0) {
628
- console.log(); // Extra newline after AI response
680
+ logger.verbose(); // Extra newline after AI response
629
681
  }
630
682
  // Convert toolCallsMap to array
631
683
  toolCalls = Object.values(toolCallsMap).filter((tc) => tc.id);
@@ -650,6 +702,7 @@ async function processStreamingResponse(stream) {
650
702
  */
651
703
  async function processIntentParserRequest(userRequest, options = {}) {
652
704
  const { modelName, conversationContext } = options;
705
+ const userDomains = extractDomains(userRequest);
653
706
  const client = createIntentParserClient(modelName);
654
707
  const modelConfig = (0, config_1.getModelConfig)(modelName);
655
708
  const config = (0, config_1.getConfig)();
@@ -691,13 +744,10 @@ async function processIntentParserRequest(userRequest, options = {}) {
691
744
  // Get the list of configured devices
692
745
  const { getConfiguredDevices } = await Promise.resolve().then(() => __importStar(require('../utilities/functions')));
693
746
  const result = await getConfiguredDevices();
694
- // Add tool result to messages and continue conversation
695
- const toolResultMessage = {
696
- role: 'tool',
697
- tool_call_id: toolCall.id,
698
- content: JSON.stringify(result),
699
- };
700
- const updatedMessages = [...messages, message, toolResultMessage];
747
+ const toolResultMessages = buildToolResponseMessages(message.tool_calls, {
748
+ [toolCall.id]: result,
749
+ });
750
+ const updatedMessages = [...messages, message, ...toolResultMessages];
701
751
  // Continue the conversation with the device list
702
752
  const followUpRequest = {
703
753
  model: modelConfig.model,
@@ -723,7 +773,7 @@ async function processIntentParserRequest(userRequest, options = {}) {
723
773
  if (followUpMessage.tool_calls && followUpMessage.tool_calls.length > 0) {
724
774
  const followUpToolCall = followUpMessage.tool_calls[0];
725
775
  if (followUpToolCall.function.name === 'parse_requirements') {
726
- const params = JSON.parse(followUpToolCall.function.arguments);
776
+ const params = normalizeRequirementDomains(JSON.parse(followUpToolCall.function.arguments), userDomains);
727
777
  // Apply constraints and defaults
728
778
  const validatedParams = (0, utils_1.applyConstraints)(params, config);
729
779
  return {
@@ -741,16 +791,13 @@ async function processIntentParserRequest(userRequest, options = {}) {
741
791
  apiKey: server.apiKey,
742
792
  })) ||
743
793
  feedConfig.baseURLs.map((url) => ({ baseUrl: url, apiKey: feedConfig.apiKey }));
744
- // Add tool result to messages and continue conversation
745
- const feedToolResultMessage = {
746
- role: 'tool',
747
- tool_call_id: followUpToolCall.id,
748
- content: JSON.stringify({ servers: serverList }),
749
- };
794
+ const feedToolResultMessages = buildToolResponseMessages(followUpMessage.tool_calls, {
795
+ [followUpToolCall.id]: { servers: serverList },
796
+ });
750
797
  const feedUpdatedMessages = [
751
798
  ...updatedMessages,
752
799
  followUpMessage,
753
- feedToolResultMessage,
800
+ ...feedToolResultMessages,
754
801
  ];
755
802
  // Continue the conversation with the feed server list
756
803
  const feedFollowUpRequest = {
@@ -778,7 +825,7 @@ async function processIntentParserRequest(userRequest, options = {}) {
778
825
  if (feedFollowUpMessage.tool_calls && feedFollowUpMessage.tool_calls.length > 0) {
779
826
  const feedToolCall = feedFollowUpMessage.tool_calls[0];
780
827
  if (feedToolCall.function.name === 'parse_requirements') {
781
- const params = JSON.parse(feedToolCall.function.arguments);
828
+ const params = normalizeRequirementDomains(JSON.parse(feedToolCall.function.arguments), userDomains);
782
829
  // Apply constraints and defaults
783
830
  const validatedParams = (0, utils_1.applyConstraints)(params, config);
784
831
  return {
@@ -886,15 +933,8 @@ async function processIntentParserRequest(userRequest, options = {}) {
886
933
  }
887
934
  }
888
935
  else {
889
- // Unhandled tool call - add assistant message and tool response to messages
890
- const toolResultMessage = {
891
- role: 'tool',
892
- tool_call_id: followUpToolCall.id,
893
- content: JSON.stringify({
894
- error: `Unknown function: ${followUpToolCall.function.name}`,
895
- }),
896
- };
897
- const validMessages = [...updatedMessages, followUpMessage, toolResultMessage];
936
+ const toolResultMessages = buildToolResponseMessages(followUpMessage.tool_calls, {});
937
+ const validMessages = [...updatedMessages, followUpMessage, ...toolResultMessages];
898
938
  // AI is still asking for more information after the error
899
939
  return {
900
940
  approved: false,
@@ -921,13 +961,10 @@ async function processIntentParserRequest(userRequest, options = {}) {
921
961
  baseUrl: server.baseUrl,
922
962
  apiKey: server.apiKey,
923
963
  })) || feedConfig.baseURLs.map((url) => ({ baseUrl: url, apiKey: feedConfig.apiKey }));
924
- // Add tool result to messages and continue conversation
925
- const toolResultMessage = {
926
- role: 'tool',
927
- tool_call_id: toolCall.id,
928
- content: JSON.stringify({ servers: serverList }),
929
- };
930
- const updatedMessages = [...messages, message, toolResultMessage];
964
+ const toolResultMessages = buildToolResponseMessages(message.tool_calls, {
965
+ [toolCall.id]: { servers: serverList },
966
+ });
967
+ const updatedMessages = [...messages, message, ...toolResultMessages];
931
968
  // Continue the conversation with the feed server list
932
969
  const followUpRequest = {
933
970
  model: modelConfig.model,
@@ -953,7 +990,7 @@ async function processIntentParserRequest(userRequest, options = {}) {
953
990
  if (followUpMessage.tool_calls && followUpMessage.tool_calls.length > 0) {
954
991
  const followUpToolCall = followUpMessage.tool_calls[0];
955
992
  if (followUpToolCall.function.name === 'parse_requirements') {
956
- const params = JSON.parse(followUpToolCall.function.arguments);
993
+ const params = normalizeRequirementDomains(JSON.parse(followUpToolCall.function.arguments), userDomains);
957
994
  // Apply constraints and defaults
958
995
  const validatedParams = (0, utils_1.applyConstraints)(params, config);
959
996
  return {
@@ -1016,7 +1053,7 @@ async function processIntentParserRequest(userRequest, options = {}) {
1016
1053
  };
1017
1054
  }
1018
1055
  else if (toolCall.function.name === 'parse_requirements') {
1019
- const params = JSON.parse(toolCall.function.arguments);
1056
+ const params = normalizeRequirementDomains(JSON.parse(toolCall.function.arguments), userDomains);
1020
1057
  // Apply constraints and defaults
1021
1058
  const validatedParams = (0, utils_1.applyConstraints)(params, config);
1022
1059
  return {
@@ -1031,17 +1068,14 @@ async function processIntentParserRequest(userRequest, options = {}) {
1031
1068
  // Validate and confirm the playlist
1032
1069
  const confirmation = await confirmPlaylistForSending(args.filePath, args.deviceName);
1033
1070
  if (!confirmation.success) {
1034
- // Add tool response message to make conversation valid
1035
- const toolResultMessage = {
1036
- role: 'tool',
1037
- tool_call_id: toolCall.id,
1038
- content: JSON.stringify({
1071
+ const toolResultMessages = buildToolResponseMessages(message.tool_calls, {
1072
+ [toolCall.id]: {
1039
1073
  success: false,
1040
1074
  error: confirmation.error,
1041
1075
  message: confirmation.message,
1042
- }),
1043
- };
1044
- const validMessages = [...messages, message, toolResultMessage];
1076
+ },
1077
+ });
1078
+ const validMessages = [...messages, message, ...toolResultMessages];
1045
1079
  // Check if this is a device selection needed case
1046
1080
  if (confirmation.needsDeviceSelection) {
1047
1081
  // Multiple devices available - ask user to choose
@@ -1122,20 +1156,20 @@ async function processIntentParserRequest(userRequest, options = {}) {
1122
1156
  }
1123
1157
  else if (toolCall.function.name === 'verify_addresses') {
1124
1158
  const args = JSON.parse(toolCall.function.arguments);
1159
+ if (Array.isArray(args.addresses) && userDomains.length > 0) {
1160
+ args.addresses = args.addresses.map((address) => normalizeDomainInput(address, userDomains));
1161
+ }
1125
1162
  const { verifyAddresses } = await Promise.resolve().then(() => __importStar(require('../utilities/functions')));
1126
1163
  const verificationResult = await verifyAddresses({ addresses: args.addresses });
1127
1164
  if (!verificationResult.valid) {
1128
- // Add tool response message for invalid addresses
1129
- const toolResultMessage = {
1130
- role: 'tool',
1131
- tool_call_id: toolCall.id,
1132
- content: JSON.stringify({
1165
+ const toolResultMessages = buildToolResponseMessages(message.tool_calls, {
1166
+ [toolCall.id]: {
1133
1167
  valid: false,
1134
1168
  errors: verificationResult.errors,
1135
1169
  results: verificationResult.results,
1136
- }),
1137
- };
1138
- const validMessages = [...messages, message, toolResultMessage];
1170
+ },
1171
+ });
1172
+ const validMessages = [...messages, message, ...toolResultMessages];
1139
1173
  // Ask user to correct the addresses
1140
1174
  return {
1141
1175
  approved: false,
@@ -1144,16 +1178,13 @@ async function processIntentParserRequest(userRequest, options = {}) {
1144
1178
  messages: validMessages,
1145
1179
  };
1146
1180
  }
1147
- // All addresses are valid - continue to next step
1148
- const toolResultMessage = {
1149
- role: 'tool',
1150
- tool_call_id: toolCall.id,
1151
- content: JSON.stringify({
1181
+ const toolResultMessages = buildToolResponseMessages(message.tool_calls, {
1182
+ [toolCall.id]: {
1152
1183
  valid: true,
1153
1184
  results: verificationResult.results,
1154
- }),
1155
- };
1156
- const validMessages = [...messages, message, toolResultMessage];
1185
+ },
1186
+ });
1187
+ const validMessages = [...messages, message, ...toolResultMessages];
1157
1188
  // Continue conversation after validation
1158
1189
  const followUpRequest = {
1159
1190
  model: modelConfig.model,
@@ -1179,7 +1210,7 @@ async function processIntentParserRequest(userRequest, options = {}) {
1179
1210
  if (followUpMessage.tool_calls && followUpMessage.tool_calls.length > 0) {
1180
1211
  const followUpToolCall = followUpMessage.tool_calls[0];
1181
1212
  if (followUpToolCall.function.name === 'parse_requirements') {
1182
- const params = JSON.parse(followUpToolCall.function.arguments);
1213
+ const params = normalizeRequirementDomains(JSON.parse(followUpToolCall.function.arguments), userDomains);
1183
1214
  // Apply constraints and defaults
1184
1215
  const validatedParams = (0, utils_1.applyConstraints)(params, config);
1185
1216
  return {
@@ -1198,12 +1229,14 @@ async function processIntentParserRequest(userRequest, options = {}) {
1198
1229
  })) ||
1199
1230
  feedConfig.baseURLs.map((url) => ({ baseUrl: url, apiKey: feedConfig.apiKey }));
1200
1231
  // Add tool result to messages and continue conversation
1201
- const feedToolResultMessage = {
1202
- role: 'tool',
1203
- tool_call_id: followUpToolCall.id,
1204
- content: JSON.stringify({ servers: serverList }),
1205
- };
1206
- const feedUpdatedMessages = [...validMessages, followUpMessage, feedToolResultMessage];
1232
+ const feedToolResultMessages = buildToolResponseMessages(followUpMessage.tool_calls, {
1233
+ [followUpToolCall.id]: { servers: serverList },
1234
+ });
1235
+ const feedUpdatedMessages = [
1236
+ ...validMessages,
1237
+ followUpMessage,
1238
+ ...feedToolResultMessages,
1239
+ ];
1207
1240
  // Continue the conversation with the feed server list
1208
1241
  const feedFollowUpRequest = {
1209
1242
  model: modelConfig.model,
@@ -1230,7 +1263,7 @@ async function processIntentParserRequest(userRequest, options = {}) {
1230
1263
  if (feedFollowUpMessage.tool_calls && feedFollowUpMessage.tool_calls.length > 0) {
1231
1264
  const feedToolCall = feedFollowUpMessage.tool_calls[0];
1232
1265
  if (feedToolCall.function.name === 'parse_requirements') {
1233
- const params = JSON.parse(feedToolCall.function.arguments);
1266
+ const params = normalizeRequirementDomains(JSON.parse(feedToolCall.function.arguments), userDomains);
1234
1267
  // Apply constraints and defaults
1235
1268
  const validatedParams = (0, utils_1.applyConstraints)(params, config);
1236
1269
  return {
@@ -1308,15 +1341,8 @@ async function processIntentParserRequest(userRequest, options = {}) {
1308
1341
  };
1309
1342
  }
1310
1343
  else {
1311
- // Unhandled tool call at top level
1312
- const toolResultMessage = {
1313
- role: 'tool',
1314
- tool_call_id: toolCall.id,
1315
- content: JSON.stringify({
1316
- error: `Unknown function: ${toolCall.function.name}`,
1317
- }),
1318
- };
1319
- const validMessages = [...messages, message, toolResultMessage];
1344
+ const toolResultMessages = buildToolResponseMessages(message.tool_calls, {});
1345
+ const validMessages = [...messages, message, ...toolResultMessages];
1320
1346
  return {
1321
1347
  approved: false,
1322
1348
  needsMoreInfo: true,
@@ -57,8 +57,11 @@ function applyConstraints(params, config) {
57
57
  // Set default quantity if not provided
58
58
  // Allow "all" as a string value for query_address type
59
59
  let quantity;
60
- if (r.quantity === 'all' || r.quantity === null || r.quantity === undefined) {
61
- quantity = r.type === 'query_address' ? 'all' : 5;
60
+ if (r.quantity === 'all') {
61
+ quantity = 'all';
62
+ }
63
+ else if (r.quantity === null || r.quantity === undefined) {
64
+ quantity = 5;
62
65
  }
63
66
  else if (typeof r.quantity === 'string') {
64
67
  // Try to parse string numbers
@@ -10,6 +10,7 @@ exports.setVerbose = setVerbose;
10
10
  exports.debug = debug;
11
11
  exports.info = info;
12
12
  exports.warn = warn;
13
+ exports.verbose = verbose;
13
14
  exports.error = error;
14
15
  exports.always = always;
15
16
  const chalk_1 = __importDefault(require("chalk"));
@@ -49,6 +50,15 @@ function warn(...args) {
49
50
  console.warn(chalk_1.default.yellow('[WARN]'), ...args);
50
51
  }
51
52
  }
53
+ /**
54
+ * Log message only in verbose mode (no prefix)
55
+ * @param {...any} args - Arguments to log
56
+ */
57
+ function verbose(...args) {
58
+ if (isVerbose) {
59
+ console.log(...args);
60
+ }
61
+ }
52
62
  /**
53
63
  * Log error message (always shown, but with more details in verbose mode)
54
64
  * @param {...any} args - Arguments to log