Skip to content

Commit

Permalink
add getIPDetails and format code for easy reading
Browse files Browse the repository at this point in the history
  • Loading branch information
allenchuang committed Dec 12, 2024
1 parent 550b110 commit f50539e
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 44 deletions.
89 changes: 46 additions & 43 deletions packages/plugin-story/src/actions/getAvailableLicenses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { storyOdyssey } from "viem/chains";

export { licenseIPTemplate };

// Types for request/response
type GetAvailableLicensesParams = {
ipid: Address;
};
Expand All @@ -24,25 +25,25 @@ type GetAvailableLicensesResponse = {
data: IPLicenseDetails[];
};

/**
* Class to handle fetching available licenses for an IP asset from Story Protocol
*/
export class GetAvailableLicensesAction {
constructor() {}
// Default query options for license terms
private readonly defaultQueryOptions = {
pagination: { limit: 10, offset: 0 },
orderBy: "blockNumber",
orderDirection: "desc",
};

async getAvailableLicenses(
params: GetAvailableLicensesParams
): Promise<GetAvailableLicensesResponse> {
const ipLicenseTermsQueryOptions = {
pagination: {
limit: 10,
offset: 0,
},
orderBy: "blockNumber",
orderDirection: "desc",
};

elizaLogger.log(
"Fetching from",
`${API_URL}/${RESOURCE_TYPE.IP_LICENSE_DETAILS}`
);

const response = await fetch(
`${API_URL}/${RESOURCE_TYPE.IP_LICENSE_DETAILS}`,
{
Expand All @@ -54,8 +55,8 @@ export class GetAvailableLicensesAction {
},
cache: "no-cache",
body: JSON.stringify({
ip_ids: [params.ipid], // Use the provided IPID instead of hardcoded value
options: ipLicenseTermsQueryOptions, // Use the defined query options
ip_ids: [params.ipid],
options: this.defaultQueryOptions,
}),
}
);
Expand All @@ -64,18 +65,37 @@ export class GetAvailableLicensesAction {
throw new Error(`HTTP error! status: ${response.status}`);
}

const text = await response.text();
try {
const text = await response.text();
const licenseDetailsResponse = JSON.parse(text);
elizaLogger.log("licenseDetailsResponse", licenseDetailsResponse);
return licenseDetailsResponse;
} catch (e) {
elizaLogger.error("Failed to parse response:", text);
elizaLogger.error("Failed to parse response");
throw new Error(`Failed to parse JSON response: ${e.message}`);
}
}
}

/**
* Formats a license's terms into a human-readable string
*/
const formatLicenseTerms = (license: IPLicenseDetails): string => {
const terms = license.terms;
return `License ID: ${license.id}
- Terms:
• Commercial Use: ${terms.commercialUse ? "Allowed" : "Not Allowed"}
• Commercial Attribution: ${terms.commercialAttribution ? "Required" : "Not Required"}
• Derivatives: ${terms.derivativesAllowed ? "Allowed" : "Not Allowed"}
• Derivatives Attribution: ${terms.derivativesAttribution ? "Required" : "Not Required"}
• Derivatives Approval: ${terms.derivativesApproval ? "Required" : "Not Required"}
• Revenue Share: ${terms.commercialRevenueShare ? terms.commercialRevenueShare + "%" : "Not Required"}
`;
};

/**
* Main action configuration for getting available licenses
*/
export const getAvailableLicensesAction = {
name: "GET_AVAILABLE_LICENSES",
description: "Get available licenses for an IP Asset on Story",
Expand All @@ -88,42 +108,27 @@ export const getAvailableLicensesAction = {
): Promise<boolean> => {
elizaLogger.log("Starting GET_AVAILABLE_LICENSES handler...");

// initialize or update state
if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}

const getAvailableLicensesContext = composeContext({
state,
template: getAvailableLicensesTemplate,
});
// Initialize or update state
state = !state
? ((await runtime.composeState(message)) as State)
: await runtime.updateRecentMessageState(state);

// Generate parameters from context
const content = await generateObjectDEPRECATED({
runtime,
context: getAvailableLicensesContext,
context: composeContext({
state,
template: getAvailableLicensesTemplate,
}),
modelClass: ModelClass.SMALL,
});

// Fetch and format license data
const action = new GetAvailableLicensesAction();
try {
const response = await action.getAvailableLicenses(content);

// TODO: need to format this better into human understandable terms
const formattedResponse = response.data
.map((license) => {
const terms = license.terms;
return `License ID: ${license.id}
- Terms:
• Commercial Use: ${terms.commercialUse ? "Allowed" : "Not Allowed"}
• Commercial Attribution: ${terms.commercialAttribution ? "Required" : "Not Required"}
• Derivatives: ${terms.derivativesAllowed ? "Allowed" : "Not Allowed"}
• Derivatives Attribution: ${terms.derivativesAttribution ? "Required" : "Not Required"}
• Derivatives Approval: ${terms.derivativesApproval ? "Required" : "Not Required"}
• Revenue Share: ${terms.commercialRevenueShare ? terms.commercialRevenueShare + "%" : "Not Required"}
`;
})
.map(formatLicenseTerms)
.join("\n");

callback?.({
Expand All @@ -141,9 +146,7 @@ export const getAvailableLicensesAction = {
}
},
template: getAvailableLicensesTemplate,
validate: async (runtime: IAgentRuntime) => {
return true;
},
validate: async () => true,
examples: [
[
{
Expand Down
132 changes: 132 additions & 0 deletions packages/plugin-story/src/actions/getIPDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import {
composeContext,
elizaLogger,
generateObjectDEPRECATED,
HandlerCallback,
ModelClass,
type IAgentRuntime,
type Memory,
type State,
} from "@ai16z/eliza";
import { getIPDetailsTemplate } from "../templates";
import { Address } from "viem";
import { Asset, RESOURCE_TYPE } from "../types/api";
import { API_URL, getResource } from "../lib/api";

export { getIPDetailsTemplate };

// Types for the action parameters and response
type GetIPDetailsParams = {
ipId: Address;
};

type GetIPDetailsResponse = {
data: Asset;
};

/**
* Class handling IP details retrieval from Story Protocol
*/
class GetIPDetailsAction {
async getIPDetails(
params: GetIPDetailsParams
): Promise<GetIPDetailsResponse> {
elizaLogger.log("Fetching from", `${API_URL}/${RESOURCE_TYPE.ASSET}`);
return (await getResource(
RESOURCE_TYPE.ASSET,
params.ipId
)) as GetIPDetailsResponse;
}
}

/**
* Formats IP asset details into a readable string
*/
const formatIPDetails = (data: Asset): string => `IP Asset Details:
ID: ${data.id}
NFT Name: ${data.nftMetadata.name}
Token Contract: ${data.nftMetadata.tokenContract}
Token ID: ${data.nftMetadata.tokenId}
Image URL: ${data.nftMetadata.imageUrl}
Relationships:
• Ancestors: ${data.ancestorCount}
• Descendants: ${data.descendantCount}
• Parents: ${data.parentCount || 0}
• Children: ${data.childCount || 0}
• Roots: ${data.rootCount || 0}
Created:
Block #${data.blockNumber}
Timestamp: ${data.blockTimestamp}`;

/**
* Main action configuration for getting IP details
*/
export const getIPDetailsAction = {
name: "GET_IP_DETAILS",
description: "Get details for an IP Asset on Story",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
options: any,
callback?: HandlerCallback
): Promise<boolean> => {
elizaLogger.log("Starting GET_IP_DETAILS handler...");

// Initialize or update state
state = !state
? ((await runtime.composeState(message)) as State)
: await runtime.updateRecentMessageState(state);

// Generate content using template
const content = await generateObjectDEPRECATED({
runtime,
context: composeContext({ state, template: getIPDetailsTemplate }),
modelClass: ModelClass.SMALL,
});

// Fetch and format IP details
const action = new GetIPDetailsAction();
try {
const response = await action.getIPDetails(content);
const formattedResponse = formatIPDetails(response.data);

callback?.({
text: formattedResponse,
action: "GET_IP_DETAILS",
source: "Story Protocol API",
});
return true;
} catch (e) {
elizaLogger.error("Error fetching IP details:", e.message);
callback?.({
text: `Error fetching IP details: ${e.message}`,
});
return false;
}
},
template: getIPDetailsTemplate,
validate: async () => true,
// Example usage of the action
examples: [
[
{
user: "assistant",
content: {
text: "Getting details for an IP Asset 0x2265F2b8e47F98b3Bdf7a1937EAc27282954A4Db",
action: "GET_IP_DETAILS",
},
},
{
user: "user",
content: {
text: "Get details for an IP Asset 0x2265F2b8e47F98b3Bdf7a1937EAc27282954A4Db",
action: "GET_IP_DETAILS",
},
},
],
],
similes: ["IP_DETAILS", "IP_DETAILS_FOR_ASSET", "IP_DETAILS_FOR_IP"],
};
10 changes: 9 additions & 1 deletion packages/plugin-story/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from "./actions/licenseIP";
export * from "./actions/attachTerms";
export * from "./providers/wallet";
export * from "./actions/getAvailableLicenses";
export * from "./actions/getIPDetails";
export * from "./providers/pinata";
export * from "./types";

Expand All @@ -12,6 +13,7 @@ import { storyPinataProvider } from "./providers/pinata";
import { registerIPAction } from "./actions/registerIP";
import { licenseIPAction } from "./actions/licenseIP";
import { getAvailableLicensesAction } from "./actions/getAvailableLicenses";
import { getIPDetailsAction } from "./actions/getIPDetails";
import { attachTermsAction } from "./actions/attachTerms";

export const storyPlugin: Plugin = {
Expand All @@ -20,7 +22,13 @@ export const storyPlugin: Plugin = {
providers: [storyWalletProvider, storyPinataProvider],
evaluators: [],
services: [],
actions: [registerIPAction, licenseIPAction, attachTermsAction, getAvailableLicensesAction],
actions: [
registerIPAction,
licenseIPAction,
attachTermsAction,
getAvailableLicensesAction,
getIPDetailsAction,
],
};

export default storyPlugin;
8 changes: 8 additions & 0 deletions packages/plugin-story/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export async function getResource(
options?: QueryOptions
) {
try {
elizaLogger.log(
`Fetching resource ${API_URL}/${resourceName}/${resourceId}`
);
const res = await fetch(`${API_URL}/${resourceName}/${resourceId}`, {
method: "GET",
headers: {
Expand All @@ -31,7 +34,12 @@ export async function getResource(
},
});
if (res.ok) {
elizaLogger.log("Response is ok");
return res.json();
} else {
elizaLogger.log("Response is not ok");
elizaLogger.log(JSON.stringify(res));
throw new Error(`HTTP error! status: ${res.status}`);
}
} catch (error) {
console.error(error);
Expand Down
16 changes: 16 additions & 0 deletions packages/plugin-story/src/templates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ Respond with a JSON markdown block containing only the extracted values:
\`\`\`
`;

export const getIPDetailsTemplate = `Given the recent messages below:
{{recentMessages}}
Extract the following information about the requested IP details:
- Field "ipId": The IP Asset that you want to get details for
Respond with a JSON markdown block containing only the extracted values:
\`\`\`json
{
"ipId": string | null
}
\`\`\`
`;

export const attachTermsTemplate = `Given the recent messages below:
{{recentMessages}}
Expand Down

0 comments on commit f50539e

Please sign in to comment.