ff1-cli 1.0.0 → 1.0.1
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/README.md +0 -8
- package/dist/index.js +411 -106
- package/dist/src/ai-orchestrator/index.js +21 -21
- package/dist/src/config.js +47 -11
- package/dist/src/intent-parser/index.js +107 -112
- package/dist/src/intent-parser/utils.js +2 -5
- package/dist/src/logger.js +1 -1
- package/dist/src/main.js +41 -28
- package/dist/src/utilities/domain-resolver.js +2 -2
- package/dist/src/utilities/feed-fetcher.js +2 -2
- package/dist/src/utilities/functions.js +12 -12
- package/dist/src/utilities/index.js +65 -14
- package/dist/src/utilities/nft-indexer.js +30 -7
- package/dist/src/utilities/playlist-send.js +18 -18
- package/dist/src/utilities/playlist-verifier.js +11 -11
- package/docs/EXAMPLES.md +9 -4
- package/docs/README.md +2 -2
- package/package.json +2 -2
|
@@ -113,38 +113,48 @@ OUTPUT CONTRACT
|
|
|
113
113
|
- Use correct types; never truncate addresses/tokenIds; tokenIds are strings; quantity is a number.
|
|
114
114
|
|
|
115
115
|
REQUIREMENT TYPES (BUILD)
|
|
116
|
-
- build_playlist: { type, blockchain: "ethereum"|"tezos", contractAddress, tokenIds
|
|
117
|
-
•
|
|
118
|
-
•
|
|
116
|
+
- build_playlist: { type, blockchain: "ethereum"|"tezos", contractAddress, tokenIds?: string[], quantity?: number, source?: string }
|
|
117
|
+
• USE THIS when user mentions "contract" with a quantity: "N items from [blockchain] contract [address]"
|
|
118
|
+
• tokenIds is OPTIONAL - omit it when user wants random tokens from a contract
|
|
119
|
+
• Examples:
|
|
120
|
+
- "tokens 1, 2, 3 from contract 0x123" → build_playlist with tokenIds: ["1", "2", "3"]
|
|
121
|
+
- "100 items from ethereum contract 0xABC" → build_playlist with quantity: 100, NO tokenIds
|
|
122
|
+
- "50 random tokens from tezos contract KT1..." → build_playlist with quantity: 50, NO tokenIds
|
|
119
123
|
- query_address: { type, ownerAddress: 0x…|tz…|domain.eth|domain.tez, quantity?: number | "all" }
|
|
120
|
-
•
|
|
121
|
-
•
|
|
122
|
-
•
|
|
123
|
-
• Example: "
|
|
124
|
+
• USE THIS for owner/wallet addresses WITHOUT the word "contract"
|
|
125
|
+
• Patterns: "N items from [address]", "NFTs from [address]", "tokens owned by [address]"
|
|
126
|
+
• Domains (.eth/.tez) are ALWAYS owner addresses
|
|
127
|
+
• Example: "100 items from 0xABC" (without mentioning "contract") → query_address
|
|
124
128
|
• When user says "all", "all tokens", "all NFTs" → use quantity="all" (string, not number)
|
|
125
|
-
• quantity="all" will fetch ALL tokens using pagination, can handle thousands of tokens
|
|
126
129
|
- fetch_feed: { type, playlistName: string, quantity?: number (default 5) }
|
|
127
130
|
|
|
131
|
+
CRITICAL DISTINCTION:
|
|
132
|
+
- User says "contract" + address → build_playlist (queries tokens FROM that contract)
|
|
133
|
+
- User says just address (no "contract" word) → query_address (queries tokens OWNED by that address)
|
|
134
|
+
- User says ".eth" or ".tez" domain → ALWAYS query_address (owner domain)
|
|
135
|
+
|
|
128
136
|
DOMAIN OWNER RULES (CRITICAL)
|
|
129
137
|
- Interpret \`*.eth\` as an Ethereum OWNER DOMAIN → produce \`query_address\` with \`ownerAddress\` set to the domain string (e.g., \`reas.eth\`).
|
|
130
138
|
- Interpret \`*.tez\` as a Tezos OWNER DOMAIN → produce \`query_address\` with \`ownerAddress\` set to the domain string (e.g., \`einstein-rosen.tez\`).
|
|
131
139
|
- Never treat \.eth or \.tez as a contract or collection identifier.
|
|
132
140
|
- Never invent or request \`tokenIds\` for \.eth/\.tez domains. Use \`quantity\` only.
|
|
133
141
|
|
|
134
|
-
EXAMPLES (query_address -
|
|
142
|
+
EXAMPLES (query_address - owner/wallet addresses)
|
|
135
143
|
- "Pick 3 artworks from reas.eth" → \`query_address\` { ownerAddress: "reas.eth", quantity: 3 }
|
|
136
|
-
- "3 from einstein-rosen.tez and play on my FF1" → \`query_address\` { ownerAddress: "einstein-rosen.tez", quantity: 3 } and set \`playlistSettings.deviceName\`
|
|
144
|
+
- "3 from einstein-rosen.tez and play on my FF1" → \`query_address\` { ownerAddress: "einstein-rosen.tez", quantity: 3 } and set \`playlistSettings.deviceName\`
|
|
137
145
|
- "create a playlist of 30 items from 0xABC" → \`query_address\` { ownerAddress: "0xABC", quantity: 30 }
|
|
138
146
|
- "get 20 NFTs from 0x123" → \`query_address\` { ownerAddress: "0x123", quantity: 20 }
|
|
139
|
-
- "create a playlist from all tokens in reas.eth" → \`query_address\` { ownerAddress: "reas.eth", quantity: "all" }
|
|
140
147
|
- "get all NFTs from 0xABC" → \`query_address\` { ownerAddress: "0xABC", quantity: "all" }
|
|
141
148
|
|
|
149
|
+
EXAMPLES (build_playlist - contract addresses)
|
|
150
|
+
- "tokens 5, 10, 15 from contract 0xABC on ethereum" → \`build_playlist\` { blockchain: "ethereum", contractAddress: "0xABC", tokenIds: ["5", "10", "15"] }
|
|
151
|
+
- "create a playlist of 100 items from ethereum contract 0xa7d8d9ef8d8ce8992df33d8b8cf4aebabd5bd270" → \`build_playlist\` { blockchain: "ethereum", contractAddress: "0xa7d8d9ef8d8ce8992df33d8b8cf4aebabd5bd270", quantity: 100 }
|
|
152
|
+
- "100 random tokens from tezos contract KT1abc" → \`build_playlist\` { blockchain: "tezos", contractAddress: "KT1abc", quantity: 100 }
|
|
153
|
+
- "get 50 from contract 0xDEF" → \`build_playlist\` { blockchain: "ethereum", contractAddress: "0xDEF", quantity: 50 }
|
|
154
|
+
|
|
142
155
|
EXAMPLES (fetch_feed)
|
|
143
156
|
- "Pick 3 artworks from Social Codes and 2 from a2p. Mix them up." → \`fetch_feed\` { playlistName: "Social Codes", quantity: 3 } + \`fetch_feed\` { playlistName: "a2p", quantity: 2 }, and set \`playlistSettings.preserveOrder\` = false
|
|
144
157
|
|
|
145
|
-
EXAMPLES (build_playlist - requires BOTH contract AND tokenIds)
|
|
146
|
-
- "tokens 5, 10, 15 from contract 0xABC on ethereum" → \`build_playlist\` { blockchain: "ethereum", contractAddress: "0xABC", tokenIds: ["5", "10", "15"] }
|
|
147
|
-
|
|
148
158
|
PLAYLIST SETTINGS EXTRACTION
|
|
149
159
|
- durationPerItem: parse phrases (e.g., "6 seconds each" → 6)
|
|
150
160
|
- preserveOrder: default true; synonyms ("shuffle", "randomize", "mix", "mix them up", "scramble") → false
|
|
@@ -167,11 +177,12 @@ MISSING INFO POLICY (ASK AT MOST ONE QUESTION)
|
|
|
167
177
|
- send: ask for device name only if user specifies a device by name and it's ambiguous; for generic references, always use get_configured_devices
|
|
168
178
|
|
|
169
179
|
ADDRESS VALIDATION (CRITICAL)
|
|
170
|
-
- When user enters any Ethereum (0x...) or Tezos (tz.../KT1) addresses, IMMEDIATELY call verify_addresses() BEFORE parsing requirements
|
|
180
|
+
- When user enters any Ethereum (0x...) or Tezos (tz.../KT1/KT...) addresses, IMMEDIATELY call verify_addresses() BEFORE parsing requirements
|
|
171
181
|
- This includes: contract addresses in build_playlist, owner addresses in query_address, or any wallet/contract address mentioned
|
|
172
182
|
- Example: user says "get tokens from 0xABC" → first call verify_addresses(['0xABC']) → get validation result → then parse_requirements
|
|
173
183
|
- If verify_addresses returns valid=false, show user the error and ask them to provide the correct address
|
|
174
|
-
- If valid=true,
|
|
184
|
+
- If valid=true, the validation result shows the blockchain type (ethereum or tezos) - use this information when parsing requirements
|
|
185
|
+
- IMPORTANT: When user explicitly mentions blockchain (e.g., "ethereum contract" or "tezos contract"), you already know the blockchain type - DO NOT ask for it again
|
|
175
186
|
|
|
176
187
|
FREE‑FORM COLLECTION NAMES
|
|
177
188
|
- Treat as fetch_feed; do not guess contracts. If user says "some", default quantity = 5.
|
|
@@ -279,7 +290,7 @@ const intentParserFunctionSchemas = [
|
|
|
279
290
|
},
|
|
280
291
|
tokenIds: {
|
|
281
292
|
type: 'array',
|
|
282
|
-
description: 'Array of token IDs to fetch - only for build_playlist type',
|
|
293
|
+
description: 'Array of specific token IDs to fetch - only for build_playlist type. OPTIONAL: omit this field when user wants random tokens from the contract (e.g., "100 items from contract"). Only include when user specifies exact token IDs (e.g., "tokens 1, 2, 3").',
|
|
283
294
|
items: {
|
|
284
295
|
type: 'string',
|
|
285
296
|
},
|
|
@@ -460,7 +471,7 @@ function formatMarkdown(text) {
|
|
|
460
471
|
formatted = formatted.replace(/\*([^*]+)\*/g, (_, p1) => chalk_1.default.italic(p1));
|
|
461
472
|
formatted = formatted.replace(/(?<!\w)_([^_]+)_(?!\w)/g, (_, p1) => chalk_1.default.italic(p1));
|
|
462
473
|
// Inline code: `code` - light grey color
|
|
463
|
-
formatted = formatted.replace(/`([^`]+)`/g, (_, p1) => chalk_1.default.
|
|
474
|
+
formatted = formatted.replace(/`([^`]+)`/g, (_, p1) => chalk_1.default.dim(p1));
|
|
464
475
|
// Links: [text](url) - show text in blue
|
|
465
476
|
formatted = formatted.replace(/\[([^\]]+)\]\([^)]+\)/g, (_, p1) => chalk_1.default.blue(p1));
|
|
466
477
|
return formatted;
|
|
@@ -482,6 +493,20 @@ function printMarkdownContent(content) {
|
|
|
482
493
|
function sleep(ms) {
|
|
483
494
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
484
495
|
}
|
|
496
|
+
function buildToolResponseMessages(toolCalls, responses) {
|
|
497
|
+
return toolCalls
|
|
498
|
+
.filter((toolCall) => toolCall.id)
|
|
499
|
+
.map((toolCall) => {
|
|
500
|
+
const content = toolCall.id && Object.prototype.hasOwnProperty.call(responses, toolCall.id)
|
|
501
|
+
? responses[toolCall.id]
|
|
502
|
+
: { error: `Unknown function: ${toolCall.function.name}` };
|
|
503
|
+
return {
|
|
504
|
+
role: 'tool',
|
|
505
|
+
tool_call_id: toolCall.id,
|
|
506
|
+
content: JSON.stringify(content),
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
}
|
|
485
510
|
async function processNonStreamingResponse(response) {
|
|
486
511
|
const message = response.choices[0]?.message;
|
|
487
512
|
if (!message) {
|
|
@@ -680,13 +705,10 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
680
705
|
// Get the list of configured devices
|
|
681
706
|
const { getConfiguredDevices } = await Promise.resolve().then(() => __importStar(require('../utilities/functions')));
|
|
682
707
|
const result = await getConfiguredDevices();
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
content: JSON.stringify(result),
|
|
688
|
-
};
|
|
689
|
-
const updatedMessages = [...messages, message, toolResultMessage];
|
|
708
|
+
const toolResultMessages = buildToolResponseMessages(message.tool_calls, {
|
|
709
|
+
[toolCall.id]: result,
|
|
710
|
+
});
|
|
711
|
+
const updatedMessages = [...messages, message, ...toolResultMessages];
|
|
690
712
|
// Continue the conversation with the device list
|
|
691
713
|
const followUpRequest = {
|
|
692
714
|
model: modelConfig.model,
|
|
@@ -730,16 +752,13 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
730
752
|
apiKey: server.apiKey,
|
|
731
753
|
})) ||
|
|
732
754
|
feedConfig.baseURLs.map((url) => ({ baseUrl: url, apiKey: feedConfig.apiKey }));
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
tool_call_id: followUpToolCall.id,
|
|
737
|
-
content: JSON.stringify({ servers: serverList }),
|
|
738
|
-
};
|
|
755
|
+
const feedToolResultMessages = buildToolResponseMessages(followUpMessage.tool_calls, {
|
|
756
|
+
[followUpToolCall.id]: { servers: serverList },
|
|
757
|
+
});
|
|
739
758
|
const feedUpdatedMessages = [
|
|
740
759
|
...updatedMessages,
|
|
741
760
|
followUpMessage,
|
|
742
|
-
|
|
761
|
+
...feedToolResultMessages,
|
|
743
762
|
];
|
|
744
763
|
// Continue the conversation with the feed server list
|
|
745
764
|
const feedFollowUpRequest = {
|
|
@@ -783,12 +802,12 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
783
802
|
console.log(chalk_1.default.cyan('Publishing to feed server...'));
|
|
784
803
|
const publishResult = await publishPlaylist(args.filePath, args.feedServer.baseUrl, args.feedServer.apiKey);
|
|
785
804
|
if (publishResult.success) {
|
|
786
|
-
console.log(chalk_1.default.green('
|
|
805
|
+
console.log(chalk_1.default.green('Published to feed server'));
|
|
787
806
|
if (publishResult.playlistId) {
|
|
788
|
-
console.log(chalk_1.default.
|
|
807
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
789
808
|
}
|
|
790
809
|
if (publishResult.feedServer) {
|
|
791
|
-
console.log(chalk_1.default.
|
|
810
|
+
console.log(chalk_1.default.dim(` Server: ${publishResult.feedServer}`));
|
|
792
811
|
}
|
|
793
812
|
console.log();
|
|
794
813
|
return {
|
|
@@ -804,9 +823,9 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
804
823
|
};
|
|
805
824
|
}
|
|
806
825
|
else {
|
|
807
|
-
console.error(chalk_1.default.red('
|
|
826
|
+
console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
|
|
808
827
|
if (publishResult.message) {
|
|
809
|
-
console.error(chalk_1.default.
|
|
828
|
+
console.error(chalk_1.default.dim(` ${publishResult.message}`));
|
|
810
829
|
}
|
|
811
830
|
console.log();
|
|
812
831
|
return {
|
|
@@ -837,12 +856,12 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
837
856
|
console.log(chalk_1.default.cyan('Publishing to feed server...'));
|
|
838
857
|
const publishResult = await publishPlaylist(args.filePath, args.feedServer.baseUrl, args.feedServer.apiKey);
|
|
839
858
|
if (publishResult.success) {
|
|
840
|
-
console.log(chalk_1.default.green('
|
|
859
|
+
console.log(chalk_1.default.green('Published to feed server'));
|
|
841
860
|
if (publishResult.playlistId) {
|
|
842
|
-
console.log(chalk_1.default.
|
|
861
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
843
862
|
}
|
|
844
863
|
if (publishResult.feedServer) {
|
|
845
|
-
console.log(chalk_1.default.
|
|
864
|
+
console.log(chalk_1.default.dim(` Server: ${publishResult.feedServer}`));
|
|
846
865
|
}
|
|
847
866
|
console.log();
|
|
848
867
|
return {
|
|
@@ -858,9 +877,9 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
858
877
|
};
|
|
859
878
|
}
|
|
860
879
|
else {
|
|
861
|
-
console.error(chalk_1.default.red('
|
|
880
|
+
console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
|
|
862
881
|
if (publishResult.message) {
|
|
863
|
-
console.error(chalk_1.default.
|
|
882
|
+
console.error(chalk_1.default.dim(` ${publishResult.message}`));
|
|
864
883
|
}
|
|
865
884
|
console.log();
|
|
866
885
|
return {
|
|
@@ -875,15 +894,8 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
875
894
|
}
|
|
876
895
|
}
|
|
877
896
|
else {
|
|
878
|
-
|
|
879
|
-
const
|
|
880
|
-
role: 'tool',
|
|
881
|
-
tool_call_id: followUpToolCall.id,
|
|
882
|
-
content: JSON.stringify({
|
|
883
|
-
error: `Unknown function: ${followUpToolCall.function.name}`,
|
|
884
|
-
}),
|
|
885
|
-
};
|
|
886
|
-
const validMessages = [...updatedMessages, followUpMessage, toolResultMessage];
|
|
897
|
+
const toolResultMessages = buildToolResponseMessages(followUpMessage.tool_calls, {});
|
|
898
|
+
const validMessages = [...updatedMessages, followUpMessage, ...toolResultMessages];
|
|
887
899
|
// AI is still asking for more information after the error
|
|
888
900
|
return {
|
|
889
901
|
approved: false,
|
|
@@ -910,13 +922,10 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
910
922
|
baseUrl: server.baseUrl,
|
|
911
923
|
apiKey: server.apiKey,
|
|
912
924
|
})) || feedConfig.baseURLs.map((url) => ({ baseUrl: url, apiKey: feedConfig.apiKey }));
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
content: JSON.stringify({ servers: serverList }),
|
|
918
|
-
};
|
|
919
|
-
const updatedMessages = [...messages, message, toolResultMessage];
|
|
925
|
+
const toolResultMessages = buildToolResponseMessages(message.tool_calls, {
|
|
926
|
+
[toolCall.id]: { servers: serverList },
|
|
927
|
+
});
|
|
928
|
+
const updatedMessages = [...messages, message, ...toolResultMessages];
|
|
920
929
|
// Continue the conversation with the feed server list
|
|
921
930
|
const followUpRequest = {
|
|
922
931
|
model: modelConfig.model,
|
|
@@ -958,12 +967,12 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
958
967
|
console.log(chalk_1.default.cyan('Publishing to feed server...'));
|
|
959
968
|
const publishResult = await publishPlaylist(args.filePath, args.feedServer.baseUrl, args.feedServer.apiKey);
|
|
960
969
|
if (publishResult.success) {
|
|
961
|
-
console.log(chalk_1.default.green('
|
|
970
|
+
console.log(chalk_1.default.green('Published to feed server'));
|
|
962
971
|
if (publishResult.playlistId) {
|
|
963
|
-
console.log(chalk_1.default.
|
|
972
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
964
973
|
}
|
|
965
974
|
if (publishResult.feedServer) {
|
|
966
|
-
console.log(chalk_1.default.
|
|
975
|
+
console.log(chalk_1.default.dim(` Server: ${publishResult.feedServer}`));
|
|
967
976
|
}
|
|
968
977
|
console.log();
|
|
969
978
|
return {
|
|
@@ -979,9 +988,9 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
979
988
|
};
|
|
980
989
|
}
|
|
981
990
|
else {
|
|
982
|
-
console.error(chalk_1.default.red('
|
|
991
|
+
console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
|
|
983
992
|
if (publishResult.message) {
|
|
984
|
-
console.error(chalk_1.default.
|
|
993
|
+
console.error(chalk_1.default.dim(` ${publishResult.message}`));
|
|
985
994
|
}
|
|
986
995
|
console.log();
|
|
987
996
|
return {
|
|
@@ -1020,17 +1029,14 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
1020
1029
|
// Validate and confirm the playlist
|
|
1021
1030
|
const confirmation = await confirmPlaylistForSending(args.filePath, args.deviceName);
|
|
1022
1031
|
if (!confirmation.success) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
role: 'tool',
|
|
1026
|
-
tool_call_id: toolCall.id,
|
|
1027
|
-
content: JSON.stringify({
|
|
1032
|
+
const toolResultMessages = buildToolResponseMessages(message.tool_calls, {
|
|
1033
|
+
[toolCall.id]: {
|
|
1028
1034
|
success: false,
|
|
1029
1035
|
error: confirmation.error,
|
|
1030
1036
|
message: confirmation.message,
|
|
1031
|
-
}
|
|
1032
|
-
};
|
|
1033
|
-
const validMessages = [...messages, message,
|
|
1037
|
+
},
|
|
1038
|
+
});
|
|
1039
|
+
const validMessages = [...messages, message, ...toolResultMessages];
|
|
1034
1040
|
// Check if this is a device selection needed case
|
|
1035
1041
|
if (confirmation.needsDeviceSelection) {
|
|
1036
1042
|
// Multiple devices available - ask user to choose
|
|
@@ -1072,12 +1078,12 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
1072
1078
|
console.log(chalk_1.default.cyan('Publishing to feed server...'));
|
|
1073
1079
|
const publishResult = await publishPlaylist(args.filePath, args.feedServer.baseUrl, args.feedServer.apiKey);
|
|
1074
1080
|
if (publishResult.success) {
|
|
1075
|
-
console.log(chalk_1.default.green('
|
|
1081
|
+
console.log(chalk_1.default.green('Published to feed server'));
|
|
1076
1082
|
if (publishResult.playlistId) {
|
|
1077
|
-
console.log(chalk_1.default.
|
|
1083
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
1078
1084
|
}
|
|
1079
1085
|
if (publishResult.feedServer) {
|
|
1080
|
-
console.log(chalk_1.default.
|
|
1086
|
+
console.log(chalk_1.default.dim(` Server: ${publishResult.feedServer}`));
|
|
1081
1087
|
}
|
|
1082
1088
|
console.log();
|
|
1083
1089
|
return {
|
|
@@ -1093,9 +1099,9 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
1093
1099
|
};
|
|
1094
1100
|
}
|
|
1095
1101
|
else {
|
|
1096
|
-
console.error(chalk_1.default.red('
|
|
1102
|
+
console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
|
|
1097
1103
|
if (publishResult.message) {
|
|
1098
|
-
console.error(chalk_1.default.
|
|
1104
|
+
console.error(chalk_1.default.dim(` ${publishResult.message}`));
|
|
1099
1105
|
}
|
|
1100
1106
|
console.log();
|
|
1101
1107
|
return {
|
|
@@ -1114,17 +1120,14 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
1114
1120
|
const { verifyAddresses } = await Promise.resolve().then(() => __importStar(require('../utilities/functions')));
|
|
1115
1121
|
const verificationResult = await verifyAddresses({ addresses: args.addresses });
|
|
1116
1122
|
if (!verificationResult.valid) {
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
role: 'tool',
|
|
1120
|
-
tool_call_id: toolCall.id,
|
|
1121
|
-
content: JSON.stringify({
|
|
1123
|
+
const toolResultMessages = buildToolResponseMessages(message.tool_calls, {
|
|
1124
|
+
[toolCall.id]: {
|
|
1122
1125
|
valid: false,
|
|
1123
1126
|
errors: verificationResult.errors,
|
|
1124
1127
|
results: verificationResult.results,
|
|
1125
|
-
}
|
|
1126
|
-
};
|
|
1127
|
-
const validMessages = [...messages, message,
|
|
1128
|
+
},
|
|
1129
|
+
});
|
|
1130
|
+
const validMessages = [...messages, message, ...toolResultMessages];
|
|
1128
1131
|
// Ask user to correct the addresses
|
|
1129
1132
|
return {
|
|
1130
1133
|
approved: false,
|
|
@@ -1133,16 +1136,13 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
1133
1136
|
messages: validMessages,
|
|
1134
1137
|
};
|
|
1135
1138
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
role: 'tool',
|
|
1139
|
-
tool_call_id: toolCall.id,
|
|
1140
|
-
content: JSON.stringify({
|
|
1139
|
+
const toolResultMessages = buildToolResponseMessages(message.tool_calls, {
|
|
1140
|
+
[toolCall.id]: {
|
|
1141
1141
|
valid: true,
|
|
1142
1142
|
results: verificationResult.results,
|
|
1143
|
-
}
|
|
1144
|
-
};
|
|
1145
|
-
const validMessages = [...messages, message,
|
|
1143
|
+
},
|
|
1144
|
+
});
|
|
1145
|
+
const validMessages = [...messages, message, ...toolResultMessages];
|
|
1146
1146
|
// Continue conversation after validation
|
|
1147
1147
|
const followUpRequest = {
|
|
1148
1148
|
model: modelConfig.model,
|
|
@@ -1187,12 +1187,14 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
1187
1187
|
})) ||
|
|
1188
1188
|
feedConfig.baseURLs.map((url) => ({ baseUrl: url, apiKey: feedConfig.apiKey }));
|
|
1189
1189
|
// Add tool result to messages and continue conversation
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1190
|
+
const feedToolResultMessages = buildToolResponseMessages(followUpMessage.tool_calls, {
|
|
1191
|
+
[followUpToolCall.id]: { servers: serverList },
|
|
1192
|
+
});
|
|
1193
|
+
const feedUpdatedMessages = [
|
|
1194
|
+
...validMessages,
|
|
1195
|
+
followUpMessage,
|
|
1196
|
+
...feedToolResultMessages,
|
|
1197
|
+
];
|
|
1196
1198
|
// Continue the conversation with the feed server list
|
|
1197
1199
|
const feedFollowUpRequest = {
|
|
1198
1200
|
model: modelConfig.model,
|
|
@@ -1235,12 +1237,12 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
1235
1237
|
console.log(chalk_1.default.cyan('Publishing to feed server...'));
|
|
1236
1238
|
const publishResult = await publishPlaylist(args.filePath, args.feedServer.baseUrl, args.feedServer.apiKey);
|
|
1237
1239
|
if (publishResult.success) {
|
|
1238
|
-
console.log(chalk_1.default.green('
|
|
1240
|
+
console.log(chalk_1.default.green('Published to feed server'));
|
|
1239
1241
|
if (publishResult.playlistId) {
|
|
1240
|
-
console.log(chalk_1.default.
|
|
1242
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
1241
1243
|
}
|
|
1242
1244
|
if (publishResult.feedServer) {
|
|
1243
|
-
console.log(chalk_1.default.
|
|
1245
|
+
console.log(chalk_1.default.dim(` Server: ${publishResult.feedServer}`));
|
|
1244
1246
|
}
|
|
1245
1247
|
console.log();
|
|
1246
1248
|
return {
|
|
@@ -1256,9 +1258,9 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
1256
1258
|
};
|
|
1257
1259
|
}
|
|
1258
1260
|
else {
|
|
1259
|
-
console.error(chalk_1.default.red('
|
|
1261
|
+
console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
|
|
1260
1262
|
if (publishResult.message) {
|
|
1261
|
-
console.error(chalk_1.default.
|
|
1263
|
+
console.error(chalk_1.default.dim(` ${publishResult.message}`));
|
|
1262
1264
|
}
|
|
1263
1265
|
console.log();
|
|
1264
1266
|
return {
|
|
@@ -1297,15 +1299,8 @@ async function processIntentParserRequest(userRequest, options = {}) {
|
|
|
1297
1299
|
};
|
|
1298
1300
|
}
|
|
1299
1301
|
else {
|
|
1300
|
-
|
|
1301
|
-
const
|
|
1302
|
-
role: 'tool',
|
|
1303
|
-
tool_call_id: toolCall.id,
|
|
1304
|
-
content: JSON.stringify({
|
|
1305
|
-
error: `Unknown function: ${toolCall.function.name}`,
|
|
1306
|
-
}),
|
|
1307
|
-
};
|
|
1308
|
-
const validMessages = [...messages, message, toolResultMessage];
|
|
1302
|
+
const toolResultMessages = buildToolResponseMessages(message.tool_calls, {});
|
|
1303
|
+
const validMessages = [...messages, message, ...toolResultMessages];
|
|
1309
1304
|
return {
|
|
1310
1305
|
approved: false,
|
|
1311
1306
|
needsMoreInfo: true,
|
|
@@ -40,9 +40,6 @@ function applyConstraints(params, config) {
|
|
|
40
40
|
if (!r.contractAddress) {
|
|
41
41
|
throw new Error(`Requirement ${index + 1}: contractAddress is required for build_playlist`);
|
|
42
42
|
}
|
|
43
|
-
if (!r.tokenIds || r.tokenIds.length === 0) {
|
|
44
|
-
throw new Error(`Requirement ${index + 1}: tokenIds are required for build_playlist`);
|
|
45
|
-
}
|
|
46
43
|
}
|
|
47
44
|
else if (r.type === 'query_address') {
|
|
48
45
|
if (!r.ownerAddress) {
|
|
@@ -87,10 +84,10 @@ function applyConstraints(params, config) {
|
|
|
87
84
|
return sum;
|
|
88
85
|
}, 0);
|
|
89
86
|
if (hasAllQuantity) {
|
|
90
|
-
console.log(chalk_1.default.yellow(`\
|
|
87
|
+
console.log(chalk_1.default.yellow(`\nRequesting all tokens from one or more addresses. This may take a while to fetch and process.\n`));
|
|
91
88
|
}
|
|
92
89
|
else if (totalRequested > 100) {
|
|
93
|
-
console.log(chalk_1.default.yellow(`\
|
|
90
|
+
console.log(chalk_1.default.yellow(`\nRequesting ${totalRequested} items. This may take a while to fetch and process.\n`));
|
|
94
91
|
}
|
|
95
92
|
// Set playlist defaults
|
|
96
93
|
if (!params.playlistSettings) {
|
package/dist/src/logger.js
CHANGED
package/dist/src/main.js
CHANGED
|
@@ -116,17 +116,30 @@ function validateRequirements(requirements) {
|
|
|
116
116
|
if (!req.blockchain) {
|
|
117
117
|
throw new Error(`Requirement ${index + 1}: blockchain is required for build_playlist`);
|
|
118
118
|
}
|
|
119
|
-
if (!req.
|
|
120
|
-
throw new Error(`Requirement ${index + 1}:
|
|
119
|
+
if (!req.contractAddress) {
|
|
120
|
+
throw new Error(`Requirement ${index + 1}: contractAddress is required for build_playlist`);
|
|
121
|
+
}
|
|
122
|
+
// tokenIds is now optional - if not provided, query random tokens from contract
|
|
123
|
+
if (req.tokenIds && req.tokenIds.length > 0) {
|
|
124
|
+
// Specific token IDs provided
|
|
125
|
+
const quantity = typeof req.quantity === 'number'
|
|
126
|
+
? Math.min(req.quantity, 20)
|
|
127
|
+
: Math.min(req.tokenIds.length, 20);
|
|
128
|
+
return {
|
|
129
|
+
...req,
|
|
130
|
+
quantity,
|
|
131
|
+
tokenIds: req.tokenIds,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// No token IDs - query random tokens from contract
|
|
136
|
+
const quantity = typeof req.quantity === 'number' ? Math.min(req.quantity, 100) : 100;
|
|
137
|
+
return {
|
|
138
|
+
...req,
|
|
139
|
+
quantity,
|
|
140
|
+
tokenIds: undefined, // Explicitly set to undefined
|
|
141
|
+
};
|
|
121
142
|
}
|
|
122
|
-
const quantity = typeof req.quantity === 'number'
|
|
123
|
-
? Math.min(req.quantity, 20)
|
|
124
|
-
: Math.min(req.tokenIds.length, 20);
|
|
125
|
-
return {
|
|
126
|
-
...req,
|
|
127
|
-
quantity,
|
|
128
|
-
tokenIds: req.tokenIds || [],
|
|
129
|
-
};
|
|
130
143
|
}
|
|
131
144
|
throw new Error(`Requirement ${index + 1}: invalid type "${req.type}"`);
|
|
132
145
|
});
|
|
@@ -210,9 +223,9 @@ async function buildPlaylist(userRequest, options = {}) {
|
|
|
210
223
|
}
|
|
211
224
|
const sendResult = await utilities.sendToDevice(confirmation.playlist, confirmation.deviceName);
|
|
212
225
|
if (sendResult.success) {
|
|
213
|
-
console.log(chalk_1.default.green('\
|
|
226
|
+
console.log(chalk_1.default.green('\nPlaylist sent'));
|
|
214
227
|
if (sendResult.deviceName) {
|
|
215
|
-
console.log(chalk_1.default.
|
|
228
|
+
console.log(chalk_1.default.dim(` Device: ${sendResult.deviceName}`));
|
|
216
229
|
}
|
|
217
230
|
console.log();
|
|
218
231
|
return {
|
|
@@ -222,9 +235,9 @@ async function buildPlaylist(userRequest, options = {}) {
|
|
|
222
235
|
};
|
|
223
236
|
}
|
|
224
237
|
console.log();
|
|
225
|
-
console.error(chalk_1.default.red('
|
|
238
|
+
console.error(chalk_1.default.red('Send failed'));
|
|
226
239
|
if (sendResult.error) {
|
|
227
|
-
console.error(chalk_1.default.red(`
|
|
240
|
+
console.error(chalk_1.default.red(` ${sendResult.error}`));
|
|
228
241
|
}
|
|
229
242
|
return {
|
|
230
243
|
success: false,
|
|
@@ -243,9 +256,9 @@ async function buildPlaylist(userRequest, options = {}) {
|
|
|
243
256
|
while (intentParserResult.needsMoreInfo) {
|
|
244
257
|
if (!interactive) {
|
|
245
258
|
// Non-interactive mode: cannot ask for clarification
|
|
246
|
-
console.error(chalk_1.default.red('\
|
|
259
|
+
console.error(chalk_1.default.red('\nNeed more information, but running in non-interactive mode. Provide a complete request.'));
|
|
247
260
|
if (intentParserResult.question) {
|
|
248
|
-
console.error(chalk_1.default.yellow('\
|
|
261
|
+
console.error(chalk_1.default.yellow('\nQuestion: ') + intentParserResult.question);
|
|
249
262
|
}
|
|
250
263
|
process.exit(1);
|
|
251
264
|
}
|
|
@@ -256,7 +269,7 @@ async function buildPlaylist(userRequest, options = {}) {
|
|
|
256
269
|
});
|
|
257
270
|
// Display the AI's question before asking for input
|
|
258
271
|
if (intentParserResult.question) {
|
|
259
|
-
console.log(chalk_1.default.cyan('\n
|
|
272
|
+
console.log(chalk_1.default.cyan('\n') + intentParserResult.question);
|
|
260
273
|
}
|
|
261
274
|
const userResponse = await new Promise((resolve) => {
|
|
262
275
|
rl.question(chalk_1.default.yellow('Your response: '), (answer) => {
|
|
@@ -265,7 +278,7 @@ async function buildPlaylist(userRequest, options = {}) {
|
|
|
265
278
|
});
|
|
266
279
|
});
|
|
267
280
|
if (!userResponse) {
|
|
268
|
-
console.error(chalk_1.default.red('\
|
|
281
|
+
console.error(chalk_1.default.red('\nNo response provided. Exiting.'));
|
|
269
282
|
process.exit(1);
|
|
270
283
|
}
|
|
271
284
|
console.log();
|
|
@@ -278,7 +291,7 @@ async function buildPlaylist(userRequest, options = {}) {
|
|
|
278
291
|
});
|
|
279
292
|
}
|
|
280
293
|
if (!intentParserResult.approved) {
|
|
281
|
-
console.error(chalk_1.default.red('\
|
|
294
|
+
console.error(chalk_1.default.red('\nRequest not approved by intent parser'));
|
|
282
295
|
return null;
|
|
283
296
|
}
|
|
284
297
|
const params = intentParserResult.params;
|
|
@@ -288,12 +301,12 @@ async function buildPlaylist(userRequest, options = {}) {
|
|
|
288
301
|
const sendParams = params;
|
|
289
302
|
const utilities = getUtilities();
|
|
290
303
|
console.log();
|
|
291
|
-
console.log(chalk_1.default.cyan('Sending to device
|
|
304
|
+
console.log(chalk_1.default.cyan('Sending to device'));
|
|
292
305
|
const sendResult = await utilities.sendToDevice(sendParams.playlist, sendParams.deviceName);
|
|
293
306
|
if (sendResult.success) {
|
|
294
|
-
console.log(chalk_1.default.green('\
|
|
307
|
+
console.log(chalk_1.default.green('\nPlaylist sent'));
|
|
295
308
|
if (sendResult.deviceName) {
|
|
296
|
-
console.log(chalk_1.default.
|
|
309
|
+
console.log(chalk_1.default.dim(` Device: ${sendResult.deviceName}`));
|
|
297
310
|
}
|
|
298
311
|
console.log();
|
|
299
312
|
return {
|
|
@@ -305,9 +318,9 @@ async function buildPlaylist(userRequest, options = {}) {
|
|
|
305
318
|
else {
|
|
306
319
|
// Send failed - return error without showing the playlist summary
|
|
307
320
|
console.log();
|
|
308
|
-
console.error(chalk_1.default.red('
|
|
321
|
+
console.error(chalk_1.default.red('Send failed'));
|
|
309
322
|
if (sendResult.error) {
|
|
310
|
-
console.error(chalk_1.default.red(`
|
|
323
|
+
console.error(chalk_1.default.red(` ${sendResult.error}`));
|
|
311
324
|
}
|
|
312
325
|
return {
|
|
313
326
|
success: false,
|
|
@@ -361,7 +374,7 @@ async function buildPlaylist(userRequest, options = {}) {
|
|
|
361
374
|
});
|
|
362
375
|
});
|
|
363
376
|
if (!userResponse) {
|
|
364
|
-
console.error(chalk_1.default.red('\
|
|
377
|
+
console.error(chalk_1.default.red('\nNo response provided. Canceling.'));
|
|
365
378
|
return null;
|
|
366
379
|
}
|
|
367
380
|
console.log();
|
|
@@ -379,14 +392,14 @@ async function buildPlaylist(userRequest, options = {}) {
|
|
|
379
392
|
}
|
|
380
393
|
// If no playlist was built, display the AI's message
|
|
381
394
|
if (!result.playlist && result.message) {
|
|
382
|
-
console.log(chalk_1.default.yellow('\n
|
|
395
|
+
console.log(chalk_1.default.yellow('\n' + result.message));
|
|
383
396
|
}
|
|
384
397
|
return result;
|
|
385
398
|
}
|
|
386
399
|
catch (error) {
|
|
387
|
-
console.error(chalk_1.default.red('\
|
|
400
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
388
401
|
if (verbose) {
|
|
389
|
-
console.error(chalk_1.default.
|
|
402
|
+
console.error(chalk_1.default.dim(error.stack));
|
|
390
403
|
}
|
|
391
404
|
throw error;
|
|
392
405
|
}
|