From e425d353d0236082eff5e503739a22ca587e769c Mon Sep 17 00:00:00 2001 From: Shaw Date: Tue, 10 Dec 2024 22:33:53 -0800 Subject: [PATCH 01/11] init commit of dominos, untested --- agent/package.json | 1 + agent/src/index.ts | 23 +- packages/plugin-dominos/.npmignore | 6 + packages/plugin-dominos/eslint.config.mjs | 3 + packages/plugin-dominos/package.json | 20 + .../plugin-dominos/src/PizzaOrderManager.ts | 718 ++++++++++++++++++ .../plugin-dominos/src/actions/endOrder.ts | 216 ++++++ packages/plugin-dominos/src/actions/index.ts | 1 + .../plugin-dominos/src/actions/startOrder.ts | 271 +++++++ .../src/actions/updateCustomer.ts | 253 ++++++ .../plugin-dominos/src/actions/updateOrder.ts | 407 ++++++++++ packages/plugin-dominos/src/docs.md | 509 +++++++++++++ packages/plugin-dominos/src/index.ts | 13 + .../plugin-dominos/src/providers/index.ts | 1 + .../src/providers/pizzaOrder.ts | 42 + packages/plugin-dominos/src/types.ts | 156 ++++ packages/plugin-dominos/tsconfig.json | 13 + packages/plugin-dominos/tsup.config.ts | 20 + pnpm-lock.yaml | 230 +++++- 19 files changed, 2853 insertions(+), 50 deletions(-) create mode 100644 packages/plugin-dominos/.npmignore create mode 100644 packages/plugin-dominos/eslint.config.mjs create mode 100644 packages/plugin-dominos/package.json create mode 100644 packages/plugin-dominos/src/PizzaOrderManager.ts create mode 100644 packages/plugin-dominos/src/actions/endOrder.ts create mode 100644 packages/plugin-dominos/src/actions/index.ts create mode 100644 packages/plugin-dominos/src/actions/startOrder.ts create mode 100644 packages/plugin-dominos/src/actions/updateCustomer.ts create mode 100644 packages/plugin-dominos/src/actions/updateOrder.ts create mode 100644 packages/plugin-dominos/src/docs.md create mode 100644 packages/plugin-dominos/src/index.ts create mode 100644 packages/plugin-dominos/src/providers/index.ts create mode 100644 packages/plugin-dominos/src/providers/pizzaOrder.ts create mode 100644 packages/plugin-dominos/src/types.ts create mode 100644 packages/plugin-dominos/tsconfig.json create mode 100644 packages/plugin-dominos/tsup.config.ts diff --git a/agent/package.json b/agent/package.json index e27d4aa5ee5..c105413ac97 100644 --- a/agent/package.json +++ b/agent/package.json @@ -29,6 +29,7 @@ "@ai16z/plugin-0g": "workspace:*", "@ai16z/plugin-aptos": "workspace:*", "@ai16z/plugin-bootstrap": "workspace:*", + "@ai16z/plugin-dominos": "workspace:*", "@ai16z/plugin-intiface": "workspace:*", "@ai16z/plugin-coinbase": "workspace:*", "@ai16z/plugin-conflux": "workspace:*", diff --git a/agent/src/index.ts b/agent/src/index.ts index 26d62c09958..ad7be3fe79f 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -3,47 +3,47 @@ import { SqliteDatabaseAdapter } from "@ai16z/adapter-sqlite"; import { AutoClientInterface } from "@ai16z/client-auto"; import { DirectClientInterface } from "@ai16z/client-direct"; import { DiscordClientInterface } from "@ai16z/client-discord"; +import { FarcasterAgentClient } from "@ai16z/client-farcaster"; import { TelegramClientInterface } from "@ai16z/client-telegram"; import { TwitterClientInterface } from "@ai16z/client-twitter"; -import { FarcasterAgentClient } from "@ai16z/client-farcaster"; import { AgentRuntime, CacheManager, Character, - Clients, DbCacheAdapter, + defaultCharacter, + elizaLogger, FsCacheAdapter, IAgentRuntime, ICacheManager, IDatabaseAdapter, IDatabaseCacheAdapter, ModelProviderName, - defaultCharacter, - elizaLogger, settings, stringToUuid, validateCharacterConfig, } from "@ai16z/eliza"; import { zgPlugin } from "@ai16z/plugin-0g"; -import createGoatPlugin from "@ai16z/plugin-goat"; import { bootstrapPlugin } from "@ai16z/plugin-bootstrap"; +import { dominosPlugin } from "@ai16z/plugin-dominos"; +import createGoatPlugin from "@ai16z/plugin-goat"; // import { intifacePlugin } from "@ai16z/plugin-intiface"; +import { aptosPlugin } from "@ai16z/plugin-aptos"; import { + advancedTradePlugin, coinbaseCommercePlugin, coinbaseMassPaymentsPlugin, - tradePlugin, tokenContractPlugin, + tradePlugin, webhookPlugin, - advancedTradePlugin, } from "@ai16z/plugin-coinbase"; import { confluxPlugin } from "@ai16z/plugin-conflux"; -import { imageGenerationPlugin } from "@ai16z/plugin-image-generation"; import { evmPlugin } from "@ai16z/plugin-evm"; +import { flowPlugin } from "@ai16z/plugin-flow"; +import { imageGenerationPlugin } from "@ai16z/plugin-image-generation"; import { createNodePlugin } from "@ai16z/plugin-node"; import { solanaPlugin } from "@ai16z/plugin-solana"; -import { teePlugin, TEEMode } from "@ai16z/plugin-tee"; -import { aptosPlugin, TransferAptosToken } from "@ai16z/plugin-aptos"; -import { flowPlugin } from "@ai16z/plugin-flow"; +import { TEEMode, teePlugin } from "@ai16z/plugin-tee"; import Database from "better-sqlite3"; import fs from "fs"; import path from "path"; @@ -396,6 +396,7 @@ export async function createAgent( character, plugins: [ bootstrapPlugin, + dominosPlugin, getSecret(character, "CONFLUX_CORE_PRIVATE_KEY") ? confluxPlugin : null, diff --git a/packages/plugin-dominos/.npmignore b/packages/plugin-dominos/.npmignore new file mode 100644 index 00000000000..078562eceab --- /dev/null +++ b/packages/plugin-dominos/.npmignore @@ -0,0 +1,6 @@ +* + +!dist/** +!package.json +!readme.md +!tsup.config.ts \ No newline at end of file diff --git a/packages/plugin-dominos/eslint.config.mjs b/packages/plugin-dominos/eslint.config.mjs new file mode 100644 index 00000000000..92fe5bbebef --- /dev/null +++ b/packages/plugin-dominos/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [...eslintGlobalConfig]; diff --git a/packages/plugin-dominos/package.json b/packages/plugin-dominos/package.json new file mode 100644 index 00000000000..aeda54a110c --- /dev/null +++ b/packages/plugin-dominos/package.json @@ -0,0 +1,20 @@ +{ + "name": "@ai16z/plugin-dominos", + "version": "0.1.5-alpha.5", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@ai16z/eliza": "workspace:*", + "dominos": "^3.3.1", + "tsup": "8.3.5" + }, + "scripts": { + "build": "tsup --format esm --dts", + "dev": "tsup --format esm --dts --watch", + "lint": "eslint . --fix" + }, + "peerDependencies": { + "whatwg-url": "7.1.0" + } +} diff --git a/packages/plugin-dominos/src/PizzaOrderManager.ts b/packages/plugin-dominos/src/PizzaOrderManager.ts new file mode 100644 index 00000000000..9ac7623e9bb --- /dev/null +++ b/packages/plugin-dominos/src/PizzaOrderManager.ts @@ -0,0 +1,718 @@ +import { IAgentRuntime, UUID } from "@ai16z/eliza"; +import { NearbyStores } from "dominos"; +import { + Customer, + ErrorType, + OrderError, + OrderItem, + OrderManager, + OrderProgress, + OrderStatus, + PaymentMethod, + PaymentStatus, + PizzaCrust, + PizzaSize, + PizzaTopping, + ToppingPortion, +} from "./types"; +import { Order } from "dominos"; + +export class PizzaOrderManager implements OrderManager { + storeId: string; + + // System state + availability = { + isStoreOpen: true, + isDeliveryAvailable: true, + isCarryoutAvailable: true, + }; + + // Required field configuration + requiredFields = { + requiresCustomerName: true, + requiresAddress: true, + requiresPayment: true, + requiresPhone: true, + requiresEmail: true, + }; + + // Payment configuration + paymentConfig = { + acceptsCash: false, + acceptsCredit: true, + requiresCVV: true, + requiresPostalCode: true, + maxFailedAttempts: 3, + }; + + // Menu configuration + private readonly menuConfig = { + defaultProductCode: "PIZZA", + basePrices: { + [PizzaSize.SMALL]: 9.99, + [PizzaSize.MEDIUM]: 11.99, + [PizzaSize.LARGE]: 13.99, + [PizzaSize.XLARGE]: 15.99, + }, + crustPrices: { + [PizzaCrust.HAND_TOSSED]: 0, + [PizzaCrust.THIN]: 0, + [PizzaCrust.PAN]: 1.0, + [PizzaCrust.GLUTEN_FREE]: 2.5, + [PizzaCrust.BROOKLYN]: 1.5, + }, + toppingPrices: { + STANDARD: 1.5, + PREMIUM: 2.5, + SPECIALTY: 3.5, + }, + toppingCategories: { + STANDARD: [ + "PEPPERONI", + "MUSHROOMS", + "ONIONS", + "GREEN_PEPPERS", + "BLACK_OLIVES", + "TOMATOES", + ], + PREMIUM: [ + "ITALIAN_SAUSAGE", + "BACON", + "EXTRA_CHEESE", + "GROUND_BEEF", + "HAM", + "PINEAPPLE", + "JALAPENOS", + ], + SPECIALTY: [ + "GRILLED_CHICKEN", + "PHILLY_STEAK", + "FETA_CHEESE", + "SPINACH", + "ANCHOVIES", + "ARTICHOKE_HEARTS", + ], + }, + availableToppings: { + // Standard Toppings + PEPPERONI: "Pepperoni", + MUSHROOMS: "Fresh Mushrooms", + ONIONS: "Fresh Onions", + GREEN_PEPPERS: "Green Peppers", + BLACK_OLIVES: "Black Olives", + TOMATOES: "Diced Tomatoes", + + // Premium Toppings + ITALIAN_SAUSAGE: "Italian Sausage", + BACON: "Applewood Smoked Bacon", + EXTRA_CHEESE: "Extra Cheese Blend", + GROUND_BEEF: "Seasoned Ground Beef", + HAM: "Premium Ham", + PINEAPPLE: "Sweet Pineapple", + JALAPENOS: "Fresh Jalapeños", + + // Specialty Toppings + GRILLED_CHICKEN: "Grilled Chicken Breast", + PHILLY_STEAK: "Premium Philly Steak", + FETA_CHEESE: "Feta Cheese", + SPINACH: "Fresh Baby Spinach", + ANCHOVIES: "Premium Anchovies", + ARTICHOKE_HEARTS: "Artichoke Hearts", + }, + specialCombos: { + MEAT_LOVERS: { + name: "Meat Lovers", + discount: 2.0, + requiredToppings: [ + "PEPPERONI", + "ITALIAN_SAUSAGE", + "BACON", + "HAM", + ], + }, + VEGGIE_SUPREME: { + name: "Veggie Supreme", + discount: 2.0, + requiredToppings: [ + "MUSHROOMS", + "GREEN_PEPPERS", + "ONIONS", + "BLACK_OLIVES", + "TOMATOES", + ], + }, + HAWAIIAN: { + name: "Hawaiian", + discount: 1.5, + requiredToppings: ["HAM", "PINEAPPLE"], + }, + SUPREME: { + name: "Supreme", + discount: 3.0, + requiredToppings: [ + "PEPPERONI", + "ITALIAN_SAUSAGE", + "MUSHROOMS", + "ONIONS", + "GREEN_PEPPERS", + ], + }, + }, + incompatibleToppings: [ + ["ANCHOVIES", "CHICKEN"], // Example of toppings that don't go well together + ["PINEAPPLE", "ANCHOVIES"], + ["ARTICHOKE_HEARTS", "GROUND_BEEF"], + ], + }; + + constructor(private runtime: IAgentRuntime) { + this.runtime = runtime; + } + + async getNearestStoreId(address: string): Promise { + try { + const nearbyStores = await new NearbyStores(address); + + if (nearbyStores.stores.length === 0) { + throw new Error("No nearby stores found."); + } + + let nearestStore: any = null; + let minDistance = Infinity; + + for (const store of nearbyStores.stores) { + if ( + store.IsOnlineCapable && + store.IsDeliveryStore && + store.IsOpen && + store.ServiceIsOpen.Delivery && + store.MinDistance < minDistance + ) { + minDistance = store.MinDistance; + nearestStore = store; + } + } + + if (!nearestStore) { + throw new Error("No open stores found for delivery."); + } + + return nearestStore.StoreID; + } catch (error) { + console.error("Error finding nearest store:", error); + throw error; + } + } + + async getOrder(userId: UUID): Promise { + const cachedOrder = await this.runtime.cacheManager.get( + `pizza-order-${userId}` + ); + return cachedOrder || null; + } + + async saveOrder(userId: UUID, order: Order): Promise { + await this.runtime.cacheManager.set(`pizza-order-${userId}`, order); + } + + async getCustomer(userId: UUID): Promise { + const customer = this.runtime.cacheManager.get( + `pizza-customer-${userId}` + ); + return customer || null; + } + + async saveCustomer(userId: UUID, customer: Customer): Promise { + await this.runtime.cacheManager.set( + `pizza-customer-${userId}`, + customer + ); + } + + // Get next required action based on order state + getNextRequiredAction(order: Order, customer: Customer): string { + if (!order.items || order.items.length === 0) { + return "Collect initial pizza order details - show size, crust, and topping options to customer"; + } + + if (!customer.name) { + return "Request customer name"; + } + + if (!customer.phone) { + return "Request customer phone number"; + } + + if (!customer.address) { + return "Request delivery address"; + } + + if (!customer.email) { + return "Request email for order confirmation"; + } + + if (order.paymentStatus === PaymentStatus.NOT_PROVIDED) { + return "Request credit card information"; + } + + if (order.paymentStatus === PaymentStatus.INVALID) { + return "Request alternative payment method"; + } + + if ( + !order.progress.isConfirmed && + order.paymentStatus === PaymentStatus.VALID + ) { + return "Review order details with customer and obtain final confirmation"; + } + + return "Provide order confirmation number and estimated delivery time"; + } + + getNextRequiredActionDialogue(order: Order, customer: Customer): string { + if (!order.items || order.items.length === 0) { + return "Let me help you build your perfect pizza! What size would you like? We have Small, Medium, Large and Extra Large. Then I can help you choose your crust type and toppings."; + } + + if (!customer.name) { + return "Could you please tell me your name for the order?"; + } + + if (!customer.phone) { + return "What phone number can we reach you at if needed?"; + } + + if (!customer.address) { + return "Where would you like your pizza delivered? Please provide your complete delivery address."; + } + + if (!customer.email) { + return "What email address should we send your order confirmation to?"; + } + + if (order.paymentStatus === PaymentStatus.NOT_PROVIDED) { + return "Great! To process your order, I'll need your credit card information. Could you please provide your card number?"; + } + + if (order.paymentStatus === PaymentStatus.INVALID) { + return "I apologize, but there seems to be an issue with that payment method. Could you please provide a different credit card?"; + } + + if ( + !order.progress.isConfirmed && + order.paymentStatus === PaymentStatus.VALID + ) { + return "Perfect! I have all your order details. Would you like me to review everything with you before finalizing your order?"; + } + + return "Great news! Your order is confirmed. Let me get your confirmation number and estimated delivery time for you."; + } + + // Get topping category and price + private getToppingInfo(toppingCode: string): { + category: string; + price: number; + } { + if (this.menuConfig.toppingCategories.STANDARD.includes(toppingCode)) { + return { + category: "STANDARD", + price: this.menuConfig.toppingPrices.STANDARD, + }; + } + if (this.menuConfig.toppingCategories.PREMIUM.includes(toppingCode)) { + return { + category: "PREMIUM", + price: this.menuConfig.toppingPrices.PREMIUM, + }; + } + if (this.menuConfig.toppingCategories.SPECIALTY.includes(toppingCode)) { + return { + category: "SPECIALTY", + price: this.menuConfig.toppingPrices.SPECIALTY, + }; + } + throw new Error(`Invalid topping code: ${toppingCode}`); + } + + // Check for special combinations + private checkSpecialCombos(toppings: PizzaTopping[]): number { + const toppingCodes = toppings.map((t) => t.code); + let maxDiscount = 0; + + for (const [_, combo] of Object.entries( + this.menuConfig.specialCombos + )) { + if (combo.requiredToppings.every((t) => toppingCodes.includes(t))) { + maxDiscount = Math.max(maxDiscount, combo.discount); + } + } + + return maxDiscount; + } + + // Format currency + private formatCurrency(amount: number): string { + return `$${amount.toFixed(2)}`; + } + + // Format topping for display with category + private formatTopping(topping: PizzaTopping): string { + const toppingInfo = this.getToppingInfo(topping.code); + const amount = topping.amount > 1 ? "Extra " : ""; + const portion = + topping.portion === ToppingPortion.ALL + ? "Whole Pizza" + : `${topping.portion} Half`; + const category = + toppingInfo.category.charAt(0) + + toppingInfo.category.slice(1).toLowerCase(); + + return ( + `${amount}${this.menuConfig.availableToppings[topping.code]} ` + + `(${portion}) - ${category} Topping` + ); + } + + // Generate detailed order summary + getOrderSummary(order: Order, customer: Customer): string { + let summary = "===== CURRENT ORDER =====\n\n"; + + // Add items + order.items.forEach((item, index) => { + summary += `PIZZA ${index + 1}\n`; + summary += `==================\n`; + summary += `Size: ${item.size} (${this.formatCurrency(this.menuConfig.basePrices[item.size])})\n`; + summary += `Crust: ${item.crust.replace("_", " ")}`; + + const crustPrice = this.menuConfig.crustPrices[item.crust]; + if (crustPrice > 0) { + summary += ` (+${this.formatCurrency(crustPrice)})\n`; + } else { + summary += "\n"; + } + + if (item.toppings && item.toppings.length > 0) { + summary += "\nTOPPINGS:\n"; + item.toppings.forEach((topping) => { + const toppingInfo = this.getToppingInfo(topping.code); + summary += `• ${this.formatTopping(topping)} `; + summary += `(+${this.formatCurrency( + toppingInfo.price * + topping.amount * + (topping.portion === ToppingPortion.ALL ? 1 : 0.5) + )})\n`; + }); + + const comboDiscount = this.checkSpecialCombos(item.toppings); + if (comboDiscount > 0) { + summary += `\nSpecial Combination Discount: -${this.formatCurrency(comboDiscount)}\n`; + } + } else { + summary += "\nClassic Cheese Pizza (No extra toppings)\n"; + } + + if (item.specialInstructions) { + summary += `\nSpecial Instructions:\n${item.specialInstructions}\n`; + } + + summary += `\nItem Total: ${this.formatCurrency(this.calculatePizzaPrice(item))}\n`; + summary += "==================\n\n"; + }); + + // Add customer info if available + if (customer) { + summary += "CUSTOMER INFORMATION\n"; + summary += "==================\n"; + if (customer.name) summary += `Name: ${customer.name}\n`; + if (customer.phone) summary += `Phone: ${customer.phone}\n`; + if (customer.address) { + summary += "Delivery Address:\n"; + summary += `${customer.address}\n`; + } + if (customer.email) summary += `Email: ${customer.email}\n`; + summary += "==================\n\n"; + } + + // Add payment info if available + if (order.paymentMethod) { + summary += "PAYMENT INFORMATION\n"; + summary += "==================\n"; + summary += `Card: ****${order.paymentMethod.cardNumber.slice(-4)}\n`; + summary += `Status: ${order.paymentStatus}\n`; + summary += "==================\n\n"; + } + + // Add order totals + summary += "ORDER TOTALS\n"; + summary += "==================\n"; + summary += `Subtotal: ${this.formatCurrency(order.total)}\n`; + const tax = order.total * 0.08; // Example tax rate + summary += `Tax (8%): ${this.formatCurrency(tax)}\n`; + const deliveryFee = 3.99; + summary += `Delivery Fee: ${this.formatCurrency(deliveryFee)}\n`; + summary += `Total: ${this.formatCurrency(order.total + tax + deliveryFee)}\n`; + summary += "==================\n"; + + return summary; + } + + // Validate pizza toppings + private validateToppings(toppings: PizzaTopping[]): OrderError | null { + for (const topping of toppings) { + // Check if topping code exists + if (!this.menuConfig.availableToppings[topping.code]) { + return { + type: ErrorType.VALIDATION_FAILED, + message: `Invalid topping code: ${topping.code}`, + code: "INVALID_TOPPING", + }; + } + + // Check if portion is valid + if (!Object.values(ToppingPortion).includes(topping.portion)) { + return { + type: ErrorType.VALIDATION_FAILED, + message: `Invalid topping portion: ${topping.portion}`, + code: "INVALID_PORTION", + }; + } + + // Check if amount is valid (1 for normal, 2 for extra) + if (topping.amount !== 1 && topping.amount !== 2) { + return { + type: ErrorType.VALIDATION_FAILED, + message: "Topping amount must be 1 (normal) or 2 (extra)", + code: "INVALID_AMOUNT", + }; + } + } + + // Check maximum number of toppings + if (toppings.length > 10) { + return { + type: ErrorType.VALIDATION_FAILED, + message: "Maximum of 10 toppings per pizza", + code: "TOO_MANY_TOPPINGS", + }; + } + + return null; + } + + // Calculate pizza price including toppings and discounts + private calculatePizzaPrice(item: OrderItem): number { + let price = + this.menuConfig.basePrices[item.size] || + this.menuConfig.basePrices[PizzaSize.MEDIUM]; + + // Add crust price + price += this.menuConfig.crustPrices[item.crust] || 0; + + // Calculate topping prices (continuing calculatePizzaPrice) + if (item.toppings) { + for (const topping of item.toppings) { + const toppingInfo = this.getToppingInfo(topping.code); + const portionMultiplier = + topping.portion === ToppingPortion.ALL ? 1 : 0.5; + price += toppingInfo.price * topping.amount * portionMultiplier; + } + + // Apply combo discounts + const comboDiscount = this.checkSpecialCombos(item.toppings); + price -= comboDiscount; + } + + return price * item.quantity; + } + + // Validate customer information + private validateCustomerInfo(customer: Customer): OrderError | null { + const phoneRegex = /^\d{3}[-.]?\d{3}[-.]?\d{4}$/; + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + const nameRegex = /^[a-zA-Z0-9\s'-]{2,50}$/; + + if (!customer.name || !nameRegex.test(customer.name)) { + return { + type: ErrorType.VALIDATION_FAILED, + message: "Please provide a valid name (2-50 characters)", + code: "INVALID_NAME", + }; + } + + if (!customer.phone || !phoneRegex.test(customer.phone)) { + return { + type: ErrorType.VALIDATION_FAILED, + message: "Please provide a valid 10-digit phone number", + code: "INVALID_PHONE", + }; + } + + if (!customer.email || !emailRegex.test(customer.email)) { + return { + type: ErrorType.VALIDATION_FAILED, + message: "Please provide a valid email address", + code: "INVALID_EMAIL", + }; + } + + if (!customer.address || customer.address.length < 10) { + return { + type: ErrorType.VALIDATION_FAILED, + message: "Please provide a complete delivery address", + code: "INVALID_ADDRESS", + }; + } + + return null; + } + + // Validate payment method + private validatePaymentMethod(payment: PaymentMethod): OrderError | null { + const cardNumberRegex = /^\d{16}$/; + const cvvRegex = /^\d{3,4}$/; + const expiryRegex = /^(0[1-9]|1[0-2])\/([0-9]{2})$/; + const postalRegex = /^\d{5}(-\d{4})?$/; + + if (!payment.cardNumber || !cardNumberRegex.test(payment.cardNumber)) { + return { + type: ErrorType.PAYMENT_FAILED, + message: "Please provide a valid 16-digit credit card number", + code: "INVALID_CARD_NUMBER", + }; + } + + if (!payment.expiryDate || !expiryRegex.test(payment.expiryDate)) { + return { + type: ErrorType.PAYMENT_FAILED, + message: "Please provide a valid expiration date (MM/YY)", + code: "INVALID_EXPIRY", + }; + } + + // Check if card is expired + if (payment.expiryDate) { + const [month, year] = payment.expiryDate.split("/"); + const expiry = new Date(2000 + parseInt(year), parseInt(month) - 1); + if (expiry < new Date()) { + return { + type: ErrorType.PAYMENT_FAILED, + message: "The card has expired", + code: "CARD_EXPIRED", + }; + } + } + + if (!payment.cvv || !cvvRegex.test(payment.cvv)) { + return { + type: ErrorType.PAYMENT_FAILED, + message: "Please provide a valid CVV (3-4 digits)", + code: "INVALID_CVV", + }; + } + + if ( + this.paymentConfig.requiresPostalCode && + (!payment.postalCode || !postalRegex.test(payment.postalCode)) + ) { + return { + type: ErrorType.PAYMENT_FAILED, + message: "Please provide a valid postal code", + code: "INVALID_POSTAL", + }; + } + + return null; + } + + // Calculate order progress + calculateOrderProgress(order: Order, customer: Customer): OrderProgress { + return { + hasCustomerInfo: !this.validateCustomerInfo(customer), + hasPaymentMethod: order.paymentMethod !== undefined, + hasValidPayment: + order.paymentStatus === PaymentStatus.VALID || + order.paymentStatus === PaymentStatus.PROCESSED, + isConfirmed: order.status === OrderStatus.CONFIRMED, + }; + } + + // Process the order + async processOrder( + order: Order, + customer: Customer + ): Promise { + // Validate pizza configuration + for (const item of order.items) { + // Validate size + if (!Object.values(PizzaSize).includes(item.size)) { + return { + type: ErrorType.VALIDATION_FAILED, + message: `Invalid pizza size: ${item.size}`, + code: "INVALID_SIZE", + }; + } + + // Validate crust + if (!Object.values(PizzaCrust).includes(item.crust)) { + return { + type: ErrorType.VALIDATION_FAILED, + message: `Invalid crust type: ${item.crust}`, + code: "INVALID_CRUST", + }; + } + + // Validate toppings + if (item.toppings) { + const toppingError = this.validateToppings(item.toppings); + if (toppingError) return toppingError; + } + + // Validate quantity + if (item.quantity < 1 || item.quantity > 10) { + return { + type: ErrorType.VALIDATION_FAILED, + message: "Quantity must be between 1 and 10", + code: "INVALID_QUANTITY", + }; + } + } + + // Calculate total price + order.total = order.items.reduce( + (total, item) => total + this.calculatePizzaPrice(item), + 0 + ); + + // Validate customer information + const customerError = this.validateCustomerInfo(customer); + if (customerError) return customerError; + + // Validate payment if provided + if (order.paymentMethod) { + const paymentError = this.validatePaymentMethod( + order.paymentMethod + ); + if (paymentError) { + order.paymentStatus = PaymentStatus.INVALID; + return paymentError; + } + order.paymentStatus = PaymentStatus.VALID; + } + + // Update order progress + order.progress = this.calculateOrderProgress(order, customer); + + // Update order status based on current state + if (!order.progress.hasCustomerInfo) { + order.status = OrderStatus.AWAITING_CUSTOMER_INFO; + } else if (!order.progress.hasValidPayment) { + order.status = OrderStatus.AWAITING_PAYMENT; + } else if (!order.progress.isConfirmed) { + order.status = OrderStatus.PROCESSING; + } else { + order.status = OrderStatus.CONFIRMED; + } + + return order; + } +} diff --git a/packages/plugin-dominos/src/actions/endOrder.ts b/packages/plugin-dominos/src/actions/endOrder.ts new file mode 100644 index 00000000000..12c5ae50d7a --- /dev/null +++ b/packages/plugin-dominos/src/actions/endOrder.ts @@ -0,0 +1,216 @@ +import { Action, ActionExample, IAgentRuntime, Memory } from "@ai16z/eliza"; +import { PizzaOrderManager } from "../PizzaOrderManager"; + +export const endOrder: Action = { + name: "CANCEL_ORDER", + description: "Ends the current pizza order and clears the order data.", + similes: ["END_ORDER", "FINISH_ORDER", "COMPLETE_ORDER", "STOP_ORDER"], + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Actually, I need to cancel my order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "nevermind, cancel the pizza order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "stop the order please", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "can you cancel my pizza order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "I changed my mind, don't want pizza anymore", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "end order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "sorry but I need to cancel this order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "stop my dominos order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "finish order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "cancel everything", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "hey can you clear my current order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "scratch that order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Your order has been canceled.", + action: "CANCEL_ORDER", + }, + }, + ], + ] as ActionExample[][], + handler: async (runtime: IAgentRuntime, message: Memory) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + + // Get the active order + const order = await orderManager.getOrder(userId); + if (!order) { + return "There is no active order to end."; + } + + // Clear the order data + await runtime.cacheManager.delete(`pizza-order-${userId}`); + await runtime.cacheManager.delete(`pizza-customer-${userId}`); + + return "Your order has been canceled."; + }, + validate: async (runtime: IAgentRuntime, message: Memory) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + + // Check if there is an active order + const existingOrder = await orderManager.getOrder(userId); + + // Only validate if there is an active order + return !!existingOrder; + }, +}; diff --git a/packages/plugin-dominos/src/actions/index.ts b/packages/plugin-dominos/src/actions/index.ts new file mode 100644 index 00000000000..b2aec27af3a --- /dev/null +++ b/packages/plugin-dominos/src/actions/index.ts @@ -0,0 +1 @@ +export * from "./startOrder.ts"; diff --git a/packages/plugin-dominos/src/actions/startOrder.ts b/packages/plugin-dominos/src/actions/startOrder.ts new file mode 100644 index 00000000000..4392f3ca8a3 --- /dev/null +++ b/packages/plugin-dominos/src/actions/startOrder.ts @@ -0,0 +1,271 @@ +import { + Action, + ActionExample, + composeContext, + generateObjectV2, + Handler, + IAgentRuntime, + Memory, + ModelClass, + State, +} from "@ai16z/eliza"; +import { Customer, Order, Item, PizzaSize, PizzaCrust } from "dominos"; +import { PizzaOrderManager } from "../PizzaOrderManager"; +import { z } from "zod"; + +const handler: Handler = async ( + runtime: IAgentRuntime, + message: Memory, + state: State +) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + + // Check for existing order + const existingOrder = await orderManager.getOrder(userId); + if (existingOrder) { + return "There is already an active order. Please complete or cancel the existing order before starting a new one."; + } + + // Extract order details from message using LLM + const extractionTemplate = ` + Extract pizza order details from the following text. Include size, crust type, toppings, quantity, and any special instructions. + If information is missing, use default values: medium size, hand tossed crust, no toppings, quantity 1. + + {{recentConversation}} + + Format the response as a JSON object with these fields: + { + "size": "SMALL"|"MEDIUM"|"LARGE"|"XLARGE", + "crust": "HAND_TOSSED"|"THIN"|"PAN"|"GLUTEN_FREE"|"BROOKLYN", + "toppings": [{"code": string, "portion": "LEFT"|"RIGHT"|"ALL", "amount": 1|2}], + "quantity": number, + "specialInstructions": string + } + `; + + const context = composeContext({ + state, + template: extractionTemplate, + }); + + const PizzaOrderSchema = z.object({ + size: z.enum(["SMALL", "MEDIUM", "LARGE", "XLARGE"]), + crust: z.enum([ + "HAND_TOSSED", + "THIN", + "PAN", + "GLUTEN_FREE", + "BROOKLYN", + ]), + toppings: z + .array( + z.object({ + code: z.string(), + portion: z.enum(["LEFT", "RIGHT", "ALL"]), + amount: z.union([z.literal(1), z.literal(2)]), + }) + ) + .optional(), + quantity: z.number().int().positive(), + specialInstructions: z.string().optional(), + }); + + try { + const orderDetails = (await generateObjectV2({ + runtime, + context, + modelClass: ModelClass.LARGE, + schema: PizzaOrderSchema, + })) as z.infer; + + // Create new order + const customer = new Customer(); + await orderManager.saveCustomer(userId, customer); + + const order = new Order(customer); + + // Add extracted item + const item = new Item({ + code: "PIZZA", + size: orderDetails.size, + crust: orderDetails.crust, + toppings: orderDetails.toppings || [], + quantity: orderDetails.quantity, + specialInstructions: orderDetails.specialInstructions, + }); + + order.addItem(item); + await orderManager.saveOrder(userId, order); + + // Generate response + let response = "Great! I've started a new pizza order for you.\n\n"; + response += orderManager.getOrderSummary(order, customer); + response += + "\n" + orderManager.getNextRequiredActionDialogue(order, customer); + + return response; + } catch (error) { + // Fallback to basic order if extraction fails + const customer = new Customer(); + await orderManager.saveCustomer(userId, customer); + + const order = new Order(customer); + const defaultItem = new Item({ + code: "PIZZA", + size: PizzaSize.MEDIUM, + crust: PizzaCrust.HAND_TOSSED, + quantity: 1, + }); + + order.addItem(defaultItem); + await orderManager.saveOrder(userId, order); + + let response = + "I've started a new pizza order with default options.\n\n"; + response += orderManager.getOrderSummary(order, customer); + response += + "\n" + orderManager.getNextRequiredActionDialogue(order, customer); + + return response; + } +}; + +export const startOrder: Action = { + name: "START_ORDER", + description: "Starts a new pizza order.", + similes: ["BEGIN_ORDER", "CREATE_ORDER", "NEW_ORDER"], + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "I'd like to order a pizza please", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll help you build your perfect pizza! What size would you like? We have Small, Medium, Large and Extra Large.", + action: "START_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Can you help me order some Dominos?", + }, + }, + { + user: "{{user2}}", + content: { + text: "Of course! I'll help you place your Dominos order. Let's start with the size - would you like a Small, Medium, Large, or Extra Large pizza?", + action: "START_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "start pizza order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Let's get your pizza order started! What size pizza would you like? We offer Small, Medium, Large and Extra Large options.", + action: "START_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "im hungry for some dominos", + }, + }, + { + user: "{{user2}}", + content: { + text: "I can help you order Dominos! First, what size pizza would you like - Small, Medium, Large, or Extra Large?", + action: "START_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "new pizza order", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll help you place your order! To get started, what size pizza would you prefer? We have Small, Medium, Large and Extra Large available.", + action: "START_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "hey can you order me a large pepperoni pizza", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll help you order a Large pizza! Before we add the pepperoni, would you like our Hand Tossed, Thin, Pan, Brooklyn, or Gluten Free crust?", + action: "START_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "begin dominos order", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll help you order from Dominos! Let's start by choosing your pizza size - would you like Small, Medium, Large, or Extra Large?", + action: "START_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "place pizza order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Happy to help you order a pizza! First things first - what size would you like? You can choose from Small, Medium, Large, or Extra Large.", + action: "START_ORDER", + }, + }, + ], + ] as ActionExample[][], + handler, + validate: async (runtime: IAgentRuntime, message: Memory) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + + // Check if there is an active order + const existingOrder = await orderManager.getOrder(userId); + + // Only validate if there is no active order + return !existingOrder; + }, +}; diff --git a/packages/plugin-dominos/src/actions/updateCustomer.ts b/packages/plugin-dominos/src/actions/updateCustomer.ts new file mode 100644 index 00000000000..350920a362e --- /dev/null +++ b/packages/plugin-dominos/src/actions/updateCustomer.ts @@ -0,0 +1,253 @@ +// TODO: +// Fix the LLM extraction to extract data using the runtime model, not gpt-4o +// Instead of a text array, generate an object with the customer fields + +import { + Action, + ActionExample, + composeContext, + generateObjectV2, + generateTextArray, + Handler, + IAgentRuntime, + Memory, + ModelClass, + State, +} from "@ai16z/eliza"; +import { Customer } from "dominos"; +import { PizzaOrderManager } from "../PizzaOrderManager"; +import { z } from "zod"; + +// Shared schemas +const CustomerSchema = z.object({ + name: z.string().min(2).optional(), + phone: z + .string() + .regex(/^\d{3}[-.]?\d{3}[-.]?\d{4}$/) + .optional(), + email: z.string().email().optional(), + address: z.string().min(10).optional(), +}); + +export const handler: Handler = async ( + runtime: IAgentRuntime, + message: Memory, + state: State +) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + + // Get active order and customer + const order = await orderManager.getOrder(userId); + if (!order) { + return "There is no active order to update customer details for. Please start a new order first."; + } + + let customer = await orderManager.getCustomer(userId); + if (!customer) { + customer = new Customer(); + } + + // Extract customer details using LLM and schema + const extractionTemplate = ` + Extract customer information from the following conversation. Keep existing information if not mentioned in the update. + + Current customer information: + Name: ${customer.name || "Not provided"} + Phone: ${customer.phone || "Not provided"} + Email: ${customer.email || "Not provided"} + Address: ${customer.address || "Not provided"} + + {{recentConversation}} + + Provide updated customer information as a JSON object, including only fields that should be changed: + { + "name": string (optional), + "phone": string (optional, format: XXX-XXX-XXXX), + "email": string (optional, valid email), + "address": string (optional, full delivery address) + } + `; + + const context = composeContext({ + state, + template: extractionTemplate, + }); + + try { + const customerUpdates = (await generateObjectV2({ + runtime, + context, + modelClass: ModelClass.LARGE, + schema: CustomerSchema, + })) as z.infer; + + // Update only provided fields + if (customerUpdates.name) customer.name = customerUpdates.name; + if (customerUpdates.phone) customer.phone = customerUpdates.phone; + if (customerUpdates.email) customer.email = customerUpdates.email; + if (customerUpdates.address) customer.address = customerUpdates.address; + + await orderManager.saveCustomer(userId, customer); + + // Process updated order + const processedOrder = await orderManager.processOrder(order, customer); + if (!("type" in processedOrder)) { + await orderManager.saveOrder(userId, processedOrder); + } + + let response = "I've updated your customer information.\n\n"; + response += orderManager.getOrderSummary(order, customer); + response += + "\n" + orderManager.getNextRequiredActionDialogue(order, customer); + + return response; + } catch (error) { + return "I couldn't understand the customer information provided. Please try again with clearer details."; + } +}; + +export const updateCustomer: Action = { + name: "UPDATE_CUSTOMER", + description: "Updates customer information based on the message text.", + similes: [ + "UPDATE_CUSTOMER_DETAILS", + "UPDATE_CUSTOMER_INFO", + "SET_CUSTOMER", + "CHANGE_CUSTOMER_INFO", + "MODIFY_CUSTOMER", + ], + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "My name is John Smith, phone number is 555-123-4567", + }, + }, + { + user: "{{user2}}", + content: { + text: "Thanks John, I've updated your contact information. Here's your updated order summary...", + action: "UPDATE_CUSTOMER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Deliver to 123 Main Street, Apt 4B, New York, NY 10001", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've updated your delivery address. Here's your updated order summary...", + action: "UPDATE_CUSTOMER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "My email is john.smith@email.com", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've added your email address. Here's your updated order summary...", + action: "UPDATE_CUSTOMER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Change my phone number to 555-987-6543", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've updated your phone number. Here's your updated order summary...", + action: "UPDATE_CUSTOMER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Update my info - Sarah Johnson, sarah.j@email.com, 555-555-5555, 456 Oak Ave, Chicago IL 60601", + }, + }, + { + user: "{{user2}}", + content: { + text: "Thanks Sarah, I've updated all your customer information. Here's your updated order summary...", + action: "UPDATE_CUSTOMER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Need to change my address to 789 Pine Street, Suite 301, Boston MA 02108", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've updated your delivery address. Here's your updated order summary...", + action: "UPDATE_CUSTOMER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Can you update my contact details? Name: Mike Wilson, Phone: 555-111-2222", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've updated your name and phone number, Mike. Here's your updated order summary...", + action: "UPDATE_CUSTOMER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Change everything to: Alex Lee, alex@email.com, 555-333-4444, 321 Maple Drive, Austin TX 78701", + }, + }, + { + user: "{{user2}}", + content: { + text: "Thanks Alex, I've updated all your customer information. Here's your updated order summary...", + action: "UPDATE_CUSTOMER", + }, + }, + ], + ] as ActionExample[][], + handler, + validate: async (runtime: IAgentRuntime, message: Memory) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + + // Check if there is an active order + const existingOrder = await orderManager.getOrder(userId); + + // Only validate if there is an active order + return !!existingOrder; + }, +}; diff --git a/packages/plugin-dominos/src/actions/updateOrder.ts b/packages/plugin-dominos/src/actions/updateOrder.ts new file mode 100644 index 00000000000..b513dce3268 --- /dev/null +++ b/packages/plugin-dominos/src/actions/updateOrder.ts @@ -0,0 +1,407 @@ +import { + Action, + ActionExample, + composeContext, + generateObjectV2, + Handler, + IAgentRuntime, + Memory, + ModelClass, + State, +} from "@ai16z/eliza"; +import { Item, PizzaCrust, PizzaSize, ToppingPortion } from "dominos"; +import { z } from "zod"; +import { PizzaOrderManager } from "../PizzaOrderManager"; + +const ModificationSchema = z.object({ + type: z.enum([ + "UPDATE_SIZE", + "UPDATE_CRUST", + "ADD_TOPPING", + "REMOVE_TOPPING", + "ADD_PIZZA", + "UPDATE_QUANTITY", + "UPDATE_INSTRUCTIONS", + ]), + itemIndex: z.number().int().min(0), + data: z.object({ + size: z.enum(["SMALL", "MEDIUM", "LARGE", "XLARGE"]).optional(), + crust: z + .enum(["HAND_TOSSED", "THIN", "PAN", "GLUTEN_FREE", "BROOKLYN"]) + .optional(), + topping: z + .object({ + code: z.string(), + portion: z.enum(["LEFT", "RIGHT", "ALL"]), + amount: z.union([z.literal(1), z.literal(2)]), + }) + .optional(), + quantity: z.number().int().positive().optional(), + specialInstructions: z.string().optional(), + newPizza: z + .object({ + size: z.enum(["SMALL", "MEDIUM", "LARGE", "XLARGE"]), + crust: z.enum([ + "HAND_TOSSED", + "THIN", + "PAN", + "GLUTEN_FREE", + "BROOKLYN", + ]), + toppings: z + .array( + z.object({ + code: z.string(), + portion: z.enum(["LEFT", "RIGHT", "ALL"]), + amount: z.union([z.literal(1), z.literal(2)]), + }) + ) + .optional(), + quantity: z.number().int().positive(), + specialInstructions: z.string().optional(), + }) + .optional(), + }), +}); + +type OrderModifications = { + modifications: z.infer[]; +}; + +export const handler: Handler = async ( + runtime: IAgentRuntime, + message: Memory, + state: State +) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + + // Get active order and customer + const order = await orderManager.getOrder(userId); + if (!order) { + return "There is no active order to update. Please start a new order first."; + } + + const customer = await orderManager.getCustomer(userId); + if (!customer) { + return "Customer details not found. Please provide customer information first."; + } + + // Extract order modifications using LLM and schema + const extractionTemplate = ` + Extract pizza order modifications from the conversation. Consider all types of changes: + - Size changes + - Crust changes + - Adding/removing toppings + - Quantity changes + - Special instructions + - Adding new pizzas + + Current order: + ${orderManager.getOrderSummary(order, customer)} + + {{recentConversation}} + + Provide the modifications as an array of change operations: + { + "modifications": [{ + "type": "UPDATE_SIZE" | "UPDATE_CRUST" | "ADD_TOPPING" | "REMOVE_TOPPING" | "ADD_PIZZA" | "UPDATE_QUANTITY" | "UPDATE_INSTRUCTIONS", + "itemIndex": number, + "data": { + // For size updates + "size": string, + // For crust updates + "crust": string, + // For topping changes + "topping": { + "code": string, + "portion": "LEFT" | "RIGHT" | "ALL", + "amount": 1 | 2 + }, + // For quantity updates + "quantity": number, + // For special instructions + "specialInstructions": string, + // For new pizzas + "newPizza": { + "size": string, + "crust": string, + "toppings": array, + "quantity": number, + "specialInstructions": string + } + } + }] + } + `; + + const context = composeContext({ + state, + template: extractionTemplate, + }); + + try { + const orderUpdates = (await generateObjectV2({ + runtime, + context, + modelClass: ModelClass.LARGE, + schema: z.object({ + modifications: z.array(ModificationSchema), + }), + })) as unknown as OrderModifications; + + // Apply modifications + for (const mod of orderUpdates.modifications) { + const item = order.items[mod.itemIndex]; + if (!item) continue; + + switch (mod.type) { + case "UPDATE_SIZE": + if (mod.data.size) item.size = mod.data.size as PizzaSize; + break; + + case "UPDATE_CRUST": + if (mod.data.crust) + item.crust = mod.data.crust as PizzaCrust; + break; + + case "ADD_TOPPING": + if (mod.data.topping) { + if (!item.toppings) item.toppings = []; + item.toppings.push({ + code: mod.data.topping.code, + portion: mod.data.topping.portion as ToppingPortion, + amount: mod.data.topping.amount, + }); + } + break; + + case "REMOVE_TOPPING": + if (mod.data.topping && item.toppings) { + item.toppings = item.toppings.filter( + (t) => + t.code !== mod.data.topping.code || + t.portion !== mod.data.topping.portion + ); + } + break; + + case "ADD_PIZZA": + if (mod.data.newPizza) { + const newItem = new Item({ + code: "PIZZA", + size: mod.data.newPizza.size as PizzaSize, + crust: mod.data.newPizza.crust as PizzaCrust, + toppings: + mod.data.newPizza.toppings?.map((t) => ({ + ...t, + portion: t.portion as ToppingPortion, + })) || [], + quantity: mod.data.newPizza.quantity, + specialInstructions: + mod.data.newPizza.specialInstructions, + }); + order.addItem(newItem); + } + break; + + case "UPDATE_QUANTITY": + if (mod.data.quantity) item.quantity = mod.data.quantity; + break; + + case "UPDATE_INSTRUCTIONS": + if (mod.data.specialInstructions) { + item.specialInstructions = mod.data.specialInstructions; + } + break; + } + } + + // Process updated order + const processedOrder = await orderManager.processOrder(order, customer); + if (!("type" in processedOrder)) { + await orderManager.saveOrder(userId, processedOrder); + } + + let response = "I've updated your order.\n\n"; + response += orderManager.getOrderSummary(order, customer); + response += + "\n" + orderManager.getNextRequiredActionDialogue(order, customer); + + return response; + } catch (error) { + return "I couldn't understand the requested changes. Please try again with clearer modifications."; + } +}; + +export const updateOrder: Action = { + name: "UPDATE_ORDER", + description: "Updates an existing pizza order with new order details.", + similes: ["MODIFY_ORDER", "CHANGE_ORDER", "SET_ORDER"], + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Can you make that a large pizza instead of medium?", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've updated your pizza size to Large. Here's your updated order summary...", + action: "UPDATE_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Add extra cheese to my pizza", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've added extra cheese to your pizza. Here's your updated order summary...", + action: "UPDATE_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Change the crust to thin crust please", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've changed your crust to Thin Crust. Here's your updated order summary...", + action: "UPDATE_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Add pepperoni to the whole pizza", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've added pepperoni to your pizza. Here's your updated order summary...", + action: "UPDATE_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Can I get mushrooms on half of it?", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've added mushrooms to half of your pizza. Here's your updated order summary...", + action: "UPDATE_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Make it a gluten free crust", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've updated your crust to Gluten Free. Note that there's a $2.50 upcharge for gluten-free crust. Here's your updated order summary...", + action: "UPDATE_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Add another pizza to my order - medium with pepperoni", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've added a Medium Pepperoni Pizza to your order. Here's your updated order summary...", + action: "UPDATE_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Can you make it well done?", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've added a special instruction for well-done cooking. Here's your updated order summary...", + action: "UPDATE_ORDER", + }, + }, + ], + ] as ActionExample[][], + handler: async (runtime: IAgentRuntime, message: Memory) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + + // Get the active order + const order = await orderManager.getOrder(userId); + if (!order) { + return "There is no active order to update. Please start a new order first."; + } + + // Get the customer details + const customer = await orderManager.getCustomer(userId); + if (!customer) { + return "Customer details not found. Please provide customer information."; + } + + // TODO: Update order details based on user input + // This could include adding/removing items, updating customer info, etc. + + // Validate and process the updated order + const processedOrder = await orderManager.processOrder(order, customer); + if (!("type" in processedOrder)) { + await orderManager.saveOrder(userId, processedOrder); + await orderManager.saveCustomer(userId, customer); + } + + // Provide updated order summary and prompt for next action + let response = "Your order has been updated.\n\n"; + response += orderManager.getOrderSummary(order, customer); + + response += orderManager.getNextRequiredActionDialogue(order, customer); + + return response; + }, + validate: async (runtime: IAgentRuntime, message: Memory) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + + // Check if there is an active order + const existingOrder = await orderManager.getOrder(userId); + + // Only validate if there is an active order + return !!existingOrder; + }, +}; diff --git a/packages/plugin-dominos/src/docs.md b/packages/plugin-dominos/src/docs.md new file mode 100644 index 00000000000..8ea39724c2d --- /dev/null +++ b/packages/plugin-dominos/src/docs.md @@ -0,0 +1,509 @@ +TLDR; Order & Track a Pizza +import {Order,Customer,Item,Payment,NearbyStores,Tracking} from 'dominos'; + +//extra cheese thin crust pizza +const pizza=new Item( +{ +//16 inch hand tossed crust +code:'16SCREEN', +options:{ +//sauce, whole pizza : normal +X: {'1/1' : '1'}, +//cheese, whole pizza : double +C: {'1/1' : '2'}, +//pepperoni, whole pizza : double +P: {'1/2' : '2'} +} +} +); + +const customer = new Customer( +{ +//this could be an Address instance if you wanted +address: '2 Portola Plaza, Monterey, Ca, 93940', +firstName: 'Brandon', +lastName: 'Miller', +//where's that 555 number from? +phone: '941-555-2368', +email: 'brandon@diginow.it' +} +); + +let storeID=0; +let distance=100; +//find the nearest store +const nearbyStores=await new NearbyStores(customer.address); +//inspect nearby stores +//console.log('\n\nNearby Stores\n\n') +//console.dir(nearbyStores,{depth:5}); + +//get closest delivery store +for(const store of nearbyStores.stores){ +//inspect each store +//console.dir(store,{depth:3}); + + if( + //we check all of these because the API responses seem to say true for some + //and false for others, but it is only reliably ok for delivery if ALL are true + //this may become an additional method on the NearbyStores class. + store.IsOnlineCapable + && store.IsDeliveryStore + && store.IsOpen + && store.ServiceIsOpen.Delivery + && store.MinDistance { +const dominos=await import('dominos'); + + //importing variables into the global like this just allows us to use the same code + //from the ESM implementation for the commonJS implementation + //This is the same as an ESM import of + +//import {Address,NearbyStores,Store,Menu,Customer,Item,Image,Order,Payment,Tracking,urls,IsDominos} from 'dominos' + + Object.assign(global,dominos); + + //need to await dominos promise completion + //because ES6 is async by nature + start(); + +})() + +function start(){ +//any of the ESM examples from the other docs will work as is here +//because we mimiced an ESM import above. + + const n='\n'; + console.log( + n, + Address,n, + NearbyStores,n, + Store,n, + Menu,n, + Customer,n, + Item,n, + Image,n, + Order,n, + Payment,n, + Tracking,n, + urls,n, + IsDominos,n + ); + +} +International Support +The module now supports using multiple sets of endpoints that we have in ./utils/urls.js or even custom endpoints. However, if you get hyour country working with custom endpoints, PLEASE CONTRIBUTE THEM BACK! You will get credit as soon as your endpoints are merged back in. + +See detailed information on how to use the international endpoints or custom endpoints here : International Dominos Endpoints and how to use them + +USA +USA is default so you really dont need to do anything other than import {urls} from 'dominos'; if you want access to the usa endpoints. + +import {urls} from 'dominos'; +console.dir(urls); + +//Or to reset the usa if you switched to custom or another country + +import {urls} from 'dominos'; +import {useInternational,canada,usa} from 'dominos/utils/urls.js'; + +//first set to canada so we can see it work since USA is default +useInternational(canada); +console.dir(urls); + +//then set it back to usa so we can confirm it works +useInternational(usa); +console.dir(urls); + +Canada +import {urls} from 'dominos'; +import {useInternational,canada} from 'dominos/utils/urls.js'; +useInternational(canada); + +console.dir(urls); +Custom +import {urls} from 'dominos'; +import {useInternational,canada} from 'dominos/utils/urls.js'; + +const myCountriesURLs={ +referer :"https://order.dominos.nz/en/pages/order/", +sourceUri :"order.dominos.nz", +location:{ +find:urls.location.find +}, +store : { +find : "https://order.dominos.nz/power/store-locator?s=${line1}&c=${line2}&type=${type}", +info : "https://order.dominos.nz/power/store/${storeID}/profile", +menu : "https://order.dominos.nz/power/store/${storeID}/menu?lang=${lang}&structured=true" +}, +order : { +validate: "https://order.dominos.nz/power/validate-order", +price : "https://order.dominos.nz/power/price-order", +place : "https://order.dominos.nz/power/place-order" +}, +track : "https://order.dominos.nz/orderstorage/GetTrackerData?" +} + +useInternational(myCountriesURLs); + +console.log('MY COUSTOM FAKE NZ ENDPOINTS'); +console.dir(urls); +Address +See the detailed docs on addresses here : Address.md + +import {Address} from 'dominos'; + +//full address examples +const address = new Address( +{ +street:'900 Clark Ave', +city:'St. Louis', +region:'MO', +postalCode:'63102' +} +); + +const address=new Address('900 Clark Ave, St. Louis, MO, 63102'); + +//partial address examples +const address = new Address( +{ +street:'900 Clark Ave', +city:'St. Louis', +postalCode:'63102' +} +); + +const address=new Address('900 Clark Ave, St. Louis, 63102'); + +//street and zip only examples +const fullAddressObject = new Address( +{ +street:'900 Clark Ave', +postalCode:'63102' +} +); + +const address=new Address('900 Clark Ave, 63102'); + +//zip only examples +const fullAddressObject = new Address( +{ +postalCode:'63102' +} +); + +const onlyZip = new Address('63102'); +NearbyStores +This provides a list of basic info on stores that are nearby an address. + +See the detailed docs on finding nearby stores here : NearbyStores.md + + import {NearbyStores, Store} from 'dominos'; + + const nearbyStores=await new NearbyStores('88 Colin P Kelly Jr St, 94107'); + + console.dir(nearbyStores,{depth:1}); + + console.log('\n\nFirst nearby store'); + console.dir(nearbyStores.stores[0],{depth:1}); + + //initialize the frst of the nearbyStores.stores + const store=await new Store(nearbyStores.stores[0].StoreID); + + console.log('\n\nFull Store info called from the first nearby stores ID'); + console.dir(store,{depth:1}); + +Menu +This provides a detailed menu for a given store. + +See the detailed docs on menus here : Menu.md + +import {Menu} from 'dominos'; + +const menu=await new Menu(4337); + +console.dir(menu,{depth:1}); +Store +This provides detailed store information. + +See the detailed docs on stores here : Store.md + + import {Store} from 'dominos'; + + const store=await new Store(4337); + + console.dir(store,{depth:1}); + +Item +Items are used to place orders. + +See the detailed docs on items here : Item.md + +import {Item} from 'dominos'; + +const pepperoniPizza=new Item( +{ +code:'P_14SCREEN' +} +) + +console.dir(pepperoniPizza); +Customer +This creates a customer object for use when making an order. + +See the detailed docs on customers here : Customer.md + +import {Customer} from 'dominos'; + +const customer = new Customer( +{ +address: '900 Clark Ave, 63102', +firstName: 'Barack', +lastName: 'Obama', +phone: '1-800-555-2368', +email: 'chief@us.gov' +} +); + +console.dir(customer); +Image +The Image class will grab the image for a product code and base 64 encode it. It extends the js-base64-file class. + +Pizza image + +See the detailed docs on image here : Image.md + +import {Image} from 'dominos'; + +const productCode='S_PIZPX'; +const savePath='./'; + +const pepperoniPizza=await new Image(productCode); +console.log(pepperoniPizza.base64Image); + +//you could pass this to a user via sms, web socket, http, tcp, make an ascii art for the terminal, OR save it to disk or a database +//here we just save it to disk as an example. +//this is part of the js-base64-file class refrence, there is a link at the top of this file +pepperoniPizza.saveSync(pepperoniPizza.base64Image,savePath,productCode+'.jpg'); +Payment +This class will initialize a creditcard payment object for an order. + +See the detailed docs on payment here : Payment.md + +import {Payment} from 'dominos'; + +const myCard=new Payment( +{ +amount:10.77, +//dashes are not needed, they just make it easier to read +//the class sanitizes the data +number:'4444-4444-4444-4444', +expiration:'01/12', +securityCode:'867', +postalCode:'93940' +} +) +Order +Finally... This class will order you pizza, and other things from the menu. + +See the detailed docs on order here : Order.md + +import {Order,Customer,Item,Payment} from 'dominos'; + +//extra cheese thin crust pizza +const pizza=new Item( +{ +code:'14THIN', +options:{ +//sauce, whole pizza : normal +X: {'1/1' : '1'}, +//cheese, whole pizza : double +C: {'1/1' : '2'} +} +} +); + +const customer = new Customer( +{ +//this could be an Address instance if you wanted +address: '110 S Fairfax Ave, 90036', +firstName: 'Barack', +lastName: 'Obama', +//where's that 555 number from? +phone: '1-800-555-2368', +email: 'chief@us.gov' +} +); + +//create +const order=new Order(customer); +order.storeID=8244; +// add pizza +order.addItem(pizza); +//validate order +await order.validate(); +//price order +await order.price(); + +//grab price from order and setup payment +const myCard=new Payment( +{ +amount:order.amountsBreakdown.customer, + + // dashes are not needed, they get filtered out + number:'4100-1234-2234-3234', + + //slashes not needed, they get filtered out + expiration:'01/35', + securityCode:'867', + postalCode:'93940' + } + +); + +order.payments.push(myCard); + +//place order +await order.place(); + +//inspect Order +console.dir(order,{depth:5}); + +//you probably want to add some tracking too... +Tracking +This is how you track Pizzas! (and other things) + +You can track its progress, who is working on it, who your delivery person is, and how many stops they have before you using this Class. + +If there are no orders for a given phone number, it will throw a DominosTrackingError. + +import {Tracking} from 'dominos'; + +const tracking=new Tracking(); + +const trackingResult=await tracking.byPhone('3108675309'); + +console.dir(trackingResult,{depth:1}); +Dominos Custom Type Checking +This class extends strong-type to allow strong and weak type checking of dominos specific types, errors and classes. It is used a lot in the dominos module to ensure correct types of arguments and errors. The strong-type module is really cool. + +See the DominosTypes.md for more information. + +import {IsDominos,Address} from 'dominos' + +isDominos=new IsDominos; + +let address='bob'; + +//address is a string so this will throw an Error +try{ +isDominos.address(address); +}catch(err){ +console.trace(err); +} + +address=new Address('1 alvarado st, 93940'); + +//will not throw because this is an Address instance +isDominos.address(address); +Global DominosErrors +These custom errors are added to the global object for use in your code and the dominos api. You can use them to validate errors or even throw your own if you are making a module ontop of this one. + +See the detailed docs on DominosErrors here : DominosErrors.md + +error parameters description +DominosValidationError .validationResponse this error is thrown when a dominos validation request fails +DominosPriceError .priceResponse this error is thrown when a dominos price request fails +DominosPlaceOrderError .placeOrderResponse this error is thrown when a dominos place request fails +DominosTrackingError message string this error is thrown when no trackable orders are found for a phone number +DominosAddressError message string this error is thrown when an issue is detected with a dominos address +DominosDateError message string this error is thrown when an issue is detected with a date being used for a dominos order +DominosStoreError message string this error is thrown when an issue is detected with a store being used for a dominos order +DominosProductsError message string this error is thrown when an issue is detected with an orders product list diff --git a/packages/plugin-dominos/src/index.ts b/packages/plugin-dominos/src/index.ts new file mode 100644 index 00000000000..79d73d61838 --- /dev/null +++ b/packages/plugin-dominos/src/index.ts @@ -0,0 +1,13 @@ +import { Plugin } from "@ai16z/eliza"; +import { startOrder } from "./actions/startOrder.ts"; +import { pizzaOrderProvider } from "./providers/pizzaOrder.ts"; + +export * as actions from "./actions/index.ts"; +export * as providers from "./providers/index.ts"; + +export const dominosPlugin: Plugin = { + name: "dominos", + description: "Order a dominos pizza", + actions: [startOrder], + providers: [pizzaOrderProvider], +}; diff --git a/packages/plugin-dominos/src/providers/index.ts b/packages/plugin-dominos/src/providers/index.ts new file mode 100644 index 00000000000..d505e75203f --- /dev/null +++ b/packages/plugin-dominos/src/providers/index.ts @@ -0,0 +1 @@ +export * from "./pizzaOrder.ts"; diff --git a/packages/plugin-dominos/src/providers/pizzaOrder.ts b/packages/plugin-dominos/src/providers/pizzaOrder.ts new file mode 100644 index 00000000000..9f67a40c8ce --- /dev/null +++ b/packages/plugin-dominos/src/providers/pizzaOrder.ts @@ -0,0 +1,42 @@ +import { IAgentRuntime, Memory, Provider } from "@ai16z/eliza"; +import { PizzaOrderManager } from "../PizzaOrderManager"; +import { OrderStatus } from "../types"; + +export const pizzaOrderProvider: Provider = { + get: async (runtime: IAgentRuntime, message: Memory) => { + const orderManager = new PizzaOrderManager(runtime); + + const userId = message.userId; + const order = await orderManager.getOrder(userId); + const customer = await orderManager.getCustomer(userId); + if (!order) { + return "No active pizza order. The customer needs to start a new order."; + } + + let context = "=== PIZZA ORDER STATUS ===\n\n"; + + // Add order summary + context += orderManager.getOrderSummary(order, customer); + + // Add next required action + context += "\nNEXT REQUIRED ACTION:\n"; + context += orderManager.getNextRequiredAction(order, customer); + + // Add store status + context += "\n\nSTORE STATUS:\n"; + context += `Store Open: ${orderManager.availability.isStoreOpen ? "Yes" : "No"}\n`; + context += `Delivery Available: ${orderManager.availability.isDeliveryAvailable ? "Yes" : "No"}\n`; + context += `Carryout Available: ${orderManager.availability.isCarryoutAvailable ? "Yes" : "No"}\n`; + + // Add order status + context += "\nORDER STATUS:\n"; + context += `Current Status: ${order.status}\n`; + if (order.status === OrderStatus.CONFIRMED) { + context += "Order is confirmed and being prepared.\n"; + } else if (order.status === OrderStatus.PROCESSING) { + context += "Order is being processed but needs confirmation.\n"; + } + + return context; + }, +}; diff --git a/packages/plugin-dominos/src/types.ts b/packages/plugin-dominos/src/types.ts new file mode 100644 index 00000000000..80d2dd61bbc --- /dev/null +++ b/packages/plugin-dominos/src/types.ts @@ -0,0 +1,156 @@ +// Order status enums +enum OrderStatus { + NEW = "NEW", + AWAITING_CUSTOMER_INFO = "AWAITING_CUSTOMER_INFO", + AWAITING_PAYMENT = "AWAITING_PAYMENT", + PROCESSING = "PROCESSING", + CONFIRMED = "CONFIRMED", + FAILED = "FAILED", +} + +// Order progress tracking +interface OrderProgress { + hasCustomerInfo: boolean; + hasPaymentMethod: boolean; + hasValidPayment: boolean; + isConfirmed: boolean; +} + +// Payment status types +enum PaymentStatus { + NOT_PROVIDED = "NOT_PROVIDED", + INVALID = "INVALID", + VALID = "VALID", + ON_FILE = "ON_FILE", + PROCESSED = "PROCESSED", +} + +// Payment method interface +interface PaymentMethod { + type: string; + cardNumber?: string; + expiryDate?: string; + cvv?: string; + postalCode?: string; + isValid: boolean; +} + +// Customer interface +interface Customer { + id?: string; + name: string; + phone: string; + email: string; + address: string; + paymentMethods?: PaymentMethod[]; + isReturning: boolean; +} + +// Pizza size enum +enum PizzaSize { + SMALL = "SMALL", + MEDIUM = "MEDIUM", + LARGE = "LARGE", + XLARGE = "XLARGE", +} + +// Pizza crust enum +enum PizzaCrust { + HAND_TOSSED = "HAND_TOSSED", + THIN = "THIN", + PAN = "PAN", + GLUTEN_FREE = "GLUTEN_FREE", + BROOKLYN = "BROOKLYN", +} + +// Topping portion enum +enum ToppingPortion { + LEFT = "LEFT", + RIGHT = "RIGHT", + ALL = "ALL", +} + +// Pizza topping interface +interface PizzaTopping { + code: string; + portion: ToppingPortion; + amount: number; // 1 for normal, 2 for extra +} + +// Order item interface +interface OrderItem { + productCode: string; + size: PizzaSize; + crust: PizzaCrust; + quantity: number; + toppings: PizzaTopping[]; + specialInstructions?: string; +} + +// Error types +enum ErrorType { + PAYMENT_FAILED = "PAYMENT_FAILED", + VALIDATION_FAILED = "VALIDATION_FAILED", + CUSTOMER_NOT_FOUND = "CUSTOMER_NOT_FOUND", + SYSTEM_ERROR = "SYSTEM_ERROR", + NETWORK_ERROR = "NETWORK_ERROR", +} + +// Custom error interface +interface OrderError { + type: ErrorType; + message: string; + code: string; + details?: any; +} + +// Order provider interface +export interface OrderManager { + storeId: string; + availability: { + isStoreOpen: boolean; + isDeliveryAvailable: boolean; + isCarryoutAvailable: boolean; + }; + requiredFields: { + requiresCustomerName: boolean; + requiresAddress: boolean; + requiresPayment: boolean; + requiresPhone: boolean; + requiresEmail: boolean; + }; + paymentConfig: { + acceptsCash: boolean; + acceptsCredit: boolean; + requiresCVV: boolean; + requiresPostalCode: boolean; + maxFailedAttempts: number; + }; +} + +// Event types for state management +type OrderEvent = + | { type: "UPDATE_CUSTOMER_INFO"; payload: Partial } + | { type: "ADD_ITEM"; payload: OrderItem } + | { type: "REMOVE_ITEM"; payload: string } + | { type: "UPDATE_PAYMENT"; payload: PaymentMethod } + | { type: "PROCESS_ORDER"; payload: Order } + | { type: "HANDLE_ERROR"; payload: OrderError }; + +// Export all types +export { + OrderStatus, + PaymentStatus, + PizzaSize, + PizzaCrust, + ToppingPortion, + ErrorType, + type OrderProgress, + type PaymentMethod, + type Customer, + type PizzaTopping, + type OrderItem, + type Order, + type OrderError, + type OrderEvent, +}; diff --git a/packages/plugin-dominos/tsconfig.json b/packages/plugin-dominos/tsconfig.json new file mode 100644 index 00000000000..834c4dce269 --- /dev/null +++ b/packages/plugin-dominos/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../core/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "types": [ + "node" + ] + }, + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/plugin-dominos/tsup.config.ts b/packages/plugin-dominos/tsup.config.ts new file mode 100644 index 00000000000..e42bf4efeae --- /dev/null +++ b/packages/plugin-dominos/tsup.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "dist", + sourcemap: true, + clean: true, + format: ["esm"], // Ensure you're targeting CommonJS + external: [ + "dotenv", // Externalize dotenv to prevent bundling + "fs", // Externalize fs to use Node.js built-in module + "path", // Externalize other built-ins if necessary + "@reflink/reflink", + "@node-llama-cpp", + "https", + "http", + "agentkeepalive", + // Add other modules you want to externalize + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c880dba4445..b003048f0a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -138,6 +138,9 @@ importers: '@ai16z/plugin-conflux': specifier: workspace:* version: link:../packages/plugin-conflux + '@ai16z/plugin-dominos': + specifier: workspace:* + version: link:../packages/plugin-dominos '@ai16z/plugin-evm': specifier: workspace:* version: link:../packages/plugin-evm @@ -585,7 +588,7 @@ importers: version: 8.57.1 jest: specifier: ^29.0.0 - version: 29.7.0(@types/node@20.17.9) + version: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) typescript: specifier: ^5.0.0 version: 5.6.3 @@ -918,6 +921,9 @@ importers: '@ai16z/eliza': specifier: workspace:* version: link:../core + dominos: + specifier: ^3.3.1 + version: 3.3.1(encoding@0.1.13) tsup: specifier: 8.3.5 version: 8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@2.4.0)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.6.1) @@ -1054,7 +1060,7 @@ importers: version: 29.5.14 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@22.8.4) + version: 29.7.0(@types/node@22.8.4)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) tsup: specifier: 8.3.5 version: 8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@2.4.0)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.6.1) @@ -1459,10 +1465,10 @@ importers: version: 8.16.0(eslint@9.16.0(jiti@2.4.0))(typescript@5.6.3) jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.17.9) + version: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) ts-jest: specifier: 29.2.5 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.9))(typescript@5.6.3) + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)))(typescript@5.6.3) typescript: specifier: 5.6.3 version: 5.6.3 @@ -7352,6 +7358,10 @@ packages: ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-colors-es6@5.0.0: + resolution: {integrity: sha512-//DAVWjZto+Mmbm8czZxrwC1/QMi5Ka+c8H6jViO1L3McHYE5YLypSFP44EyrJVzPnTnnxOsjOHjLB262eNoDA==} + engines: {node: '>=13'} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -7737,6 +7747,10 @@ packages: bip39@3.1.0: resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==} + biskviit@1.0.1: + resolution: {integrity: sha512-VGCXdHbdbpEkFgtjkeoBN8vRlbj1ZRX2/mxhE8asCCRalUx2nBzOomLJv8Aw/nRt5+ccDb+tPKidg4XxcfGW4w==} + engines: {node: '>=1.0.0'} + bitcoinjs-lib@7.0.0-rc.0: resolution: {integrity: sha512-7CQgOIbREemKR/NT2uc3uO/fkEy+6CM0sLxboVVY6bv6DbZmPt3gg5Y/hhWgQFeZu5lfTbtVAv32MIxf7lMh4g==} engines: {node: '>=18.0.0'} @@ -9222,6 +9236,10 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dominos@3.3.1: + resolution: {integrity: sha512-CAXN0K71Jlsl4iOAzu95p/lFFVxeQSbtn0yz/24SIpU8EMcRa8ZVjxjJjPBsxnauyM0itbxBPTqiZoj4oBn6WA==} + engines: {node: '>=14.0.0'} + dompurify@3.2.2: resolution: {integrity: sha512-YMM+erhdZ2nkZ4fTNRTSI94mb7VG7uVF5vj5Zde7tImgnhZE3R6YW/IACGIHb2ux+QkEXMhe591N+5jWOmL4Zw==} @@ -9350,6 +9368,9 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + encoding@0.1.12: + resolution: {integrity: sha512-bl1LAgiQc4ZWr++pNYUdRe/alecaHFeHxIJ/pNciqGdKXghaTCOwKkbKp6ye7pKZGu/GcaSXFk8PBVhgs+dJdA==} + encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -9815,6 +9836,9 @@ packages: fetch-cookie@3.0.1: resolution: {integrity: sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q==} + fetch@1.1.0: + resolution: {integrity: sha512-5O8TwrGzoNblBG/jtK4NFuZwNCkZX6s5GfRNOaGtm+QGJEuNakSC/i2RW0R93KX6E0jVjNXm6O3CRN4Ql3K+yA==} + ffmpeg-static@5.2.0: resolution: {integrity: sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==} engines: {node: '>=16'} @@ -11245,6 +11269,9 @@ packages: jpeg-js@0.3.7: resolution: {integrity: sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==} + js-base64-file@2.0.3: + resolution: {integrity: sha512-rcstfEIC1trslH4kZkHqiwBqy/jUf8SnmdV5FH6YzBQ2jhMOTIH5Mem6GcDP1sVcm5Lg/1JmS71m5jUPJGXolg==} + js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} @@ -15307,6 +15334,14 @@ packages: engines: {node: '>=4'} hasBin: true + strong-type@0.1.6: + resolution: {integrity: sha512-eJe5caH6Pi5oMMeQtIoBPpvNu/s4jiyb63u5tkHNnQXomK+puyQ5i+Z5iTLBr/xUz/pIcps0NSfzzFI34+gAXg==} + engines: {node: '>=12.0.0'} + + strong-type@1.1.0: + resolution: {integrity: sha512-X5Z6riticuH5GnhUyzijfDi1SoXas8ODDyN7K8lJeQK+Jfi4dKdoJGL4CXTskY/ATBcN+rz5lROGn1tAUkOX7g==} + engines: {node: '>=12.21.0'} + style-to-object@0.4.4: resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} @@ -16265,6 +16300,10 @@ packages: value-equal@1.0.1: resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==} + vanilla-test@1.4.9: + resolution: {integrity: sha512-QgmwI+b1RZ4xCePfezgNMfR8J5EWd0LnwmGcd3CFQWOOgH360Gww6U8RCk04TGSCn+W4oVB2biCqFc5mmYiJww==} + engines: {node: '>=12.21.0'} + varuint-bitcoin@2.0.0: resolution: {integrity: sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==} @@ -16736,6 +16775,14 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} + xml2js@0.4.23: + resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -20854,6 +20901,41 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.9 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3))': dependencies: '@jest/console': 29.7.0 @@ -21845,7 +21927,7 @@ snapshots: '@octokit/request-error': 3.0.3 '@octokit/types': 9.3.2 is-plain-object: 5.0.0 - node-fetch: 2.6.7(encoding@0.1.13) + node-fetch: 2.7.0(encoding@0.1.13) universal-user-agent: 6.0.1 transitivePeerDependencies: - encoding @@ -25335,6 +25417,8 @@ snapshots: dependencies: string-width: 4.2.3 + ansi-colors-es6@5.0.0: {} + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -25761,6 +25845,10 @@ snapshots: dependencies: '@noble/hashes': 1.6.1 + biskviit@1.0.1: + dependencies: + psl: 1.15.0 + bitcoinjs-lib@7.0.0-rc.0(typescript@5.6.3): dependencies: '@noble/hashes': 1.6.1 @@ -26688,13 +26776,13 @@ snapshots: ripemd160: 2.0.2 sha.js: 2.4.11 - create-jest@29.7.0(@types/node@20.17.9): + create-jest@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -27461,6 +27549,16 @@ snapshots: dependencies: domelementtype: 2.3.0 + dominos@3.3.1(encoding@0.1.13): + dependencies: + js-base64-file: 2.0.3(encoding@0.1.13) + node-fetch: 2.7.0(encoding@0.1.13) + strong-type: 0.1.6 + vanilla-test: 1.4.9 + xml2js: 0.4.23 + transitivePeerDependencies: + - encoding + dompurify@3.2.2: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -27642,6 +27740,10 @@ snapshots: encodeurl@2.0.0: {} + encoding@0.1.12: + dependencies: + iconv-lite: 0.4.24 + encoding@0.1.13: dependencies: iconv-lite: 0.6.3 @@ -28297,6 +28399,11 @@ snapshots: set-cookie-parser: 2.7.1 tough-cookie: 4.1.4 + fetch@1.1.0: + dependencies: + biskviit: 1.0.1 + encoding: 0.1.12 + ffmpeg-static@5.2.0: dependencies: '@derhuerst/http-basic': 8.2.4 @@ -29814,16 +29921,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.17.9): + jest-cli@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.17.9) + create-jest: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -29833,7 +29940,7 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@22.8.4): + jest-cli@29.7.0(@types/node@22.8.4)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) '@jest/test-result': 29.7.0 @@ -29852,24 +29959,36 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@22.8.4)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)): + jest-config@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) - '@jest/test-result': 29.7.0 + '@babel/core': 7.26.0 + '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.8.4)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.8.4)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - yargs: 17.7.2 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.17.9 + ts-node: 10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3) transitivePeerDependencies: - - '@types/node' - babel-plugin-macros - supports-color - - ts-node jest-config@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)): dependencies: @@ -30154,24 +30273,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.17.9): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.17.9) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest@29.7.0(@types/node@22.8.4): + jest@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.8.4) + jest-cli: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -30208,6 +30315,14 @@ snapshots: jpeg-js@0.3.7: {} + js-base64-file@2.0.3(encoding@0.1.13): + dependencies: + fetch: 1.1.0 + node-fetch: 2.7.0(encoding@0.1.13) + strong-type: 0.1.6 + transitivePeerDependencies: + - encoding + js-base64@3.7.7: {} js-git@0.7.8: @@ -35228,6 +35343,10 @@ snapshots: minimist: 1.2.8 through: 2.3.8 + strong-type@0.1.6: {} + + strong-type@1.1.0: {} + style-to-object@0.4.4: dependencies: inline-style-parser: 0.1.1 @@ -35635,12 +35754,12 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.9))(typescript@5.6.3): + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)))(typescript@5.6.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.17.9) + jest: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -35675,6 +35794,27 @@ snapshots: ts-mixer@6.0.4: {} + ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.17.9 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.6.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.10.1(@swc/helpers@0.5.15) + optional: true + ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -36196,6 +36336,11 @@ snapshots: value-equal@1.0.1: {} + vanilla-test@1.4.9: + dependencies: + ansi-colors-es6: 5.0.0 + strong-type: 1.1.0 + varuint-bitcoin@2.0.0: dependencies: uint8array-tools: 0.0.8 @@ -36790,6 +36935,13 @@ snapshots: xml-name-validator@5.0.0: {} + xml2js@0.4.23: + dependencies: + sax: 1.4.1 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + xmlchars@2.2.0: {} xtend@4.0.2: {} From 0d33b026dbcbb82ef7ea332cc96a6c12832f70d5 Mon Sep 17 00:00:00 2001 From: Shaw Date: Tue, 10 Dec 2024 22:40:03 -0800 Subject: [PATCH 02/11] import types from dominos --- .../plugin-dominos/src/PizzaOrderManager.ts | 6 +- .../plugin-dominos/src/actions/startOrder.ts | 4 +- .../src/actions/updateCustomer.ts | 7 +- .../src/providers/pizzaOrder.ts | 2 +- packages/plugin-dominos/src/types.ts | 156 ------------------ 5 files changed, 7 insertions(+), 168 deletions(-) delete mode 100644 packages/plugin-dominos/src/types.ts diff --git a/packages/plugin-dominos/src/PizzaOrderManager.ts b/packages/plugin-dominos/src/PizzaOrderManager.ts index 9ac7623e9bb..a499f0fb6e5 100644 --- a/packages/plugin-dominos/src/PizzaOrderManager.ts +++ b/packages/plugin-dominos/src/PizzaOrderManager.ts @@ -1,6 +1,7 @@ import { IAgentRuntime, UUID } from "@ai16z/eliza"; -import { NearbyStores } from "dominos"; import { + NearbyStores, + Order, Customer, ErrorType, OrderError, @@ -14,8 +15,7 @@ import { PizzaSize, PizzaTopping, ToppingPortion, -} from "./types"; -import { Order } from "dominos"; +} from "dominos"; export class PizzaOrderManager implements OrderManager { storeId: string; diff --git a/packages/plugin-dominos/src/actions/startOrder.ts b/packages/plugin-dominos/src/actions/startOrder.ts index 4392f3ca8a3..22e33ab2289 100644 --- a/packages/plugin-dominos/src/actions/startOrder.ts +++ b/packages/plugin-dominos/src/actions/startOrder.ts @@ -9,9 +9,9 @@ import { ModelClass, State, } from "@ai16z/eliza"; -import { Customer, Order, Item, PizzaSize, PizzaCrust } from "dominos"; -import { PizzaOrderManager } from "../PizzaOrderManager"; +import { Customer, Item, Order, PizzaCrust, PizzaSize } from "dominos"; import { z } from "zod"; +import { PizzaOrderManager } from "../PizzaOrderManager"; const handler: Handler = async ( runtime: IAgentRuntime, diff --git a/packages/plugin-dominos/src/actions/updateCustomer.ts b/packages/plugin-dominos/src/actions/updateCustomer.ts index 350920a362e..ee6f16de278 100644 --- a/packages/plugin-dominos/src/actions/updateCustomer.ts +++ b/packages/plugin-dominos/src/actions/updateCustomer.ts @@ -1,13 +1,8 @@ -// TODO: -// Fix the LLM extraction to extract data using the runtime model, not gpt-4o -// Instead of a text array, generate an object with the customer fields - import { Action, ActionExample, composeContext, generateObjectV2, - generateTextArray, Handler, IAgentRuntime, Memory, @@ -15,8 +10,8 @@ import { State, } from "@ai16z/eliza"; import { Customer } from "dominos"; -import { PizzaOrderManager } from "../PizzaOrderManager"; import { z } from "zod"; +import { PizzaOrderManager } from "../PizzaOrderManager"; // Shared schemas const CustomerSchema = z.object({ diff --git a/packages/plugin-dominos/src/providers/pizzaOrder.ts b/packages/plugin-dominos/src/providers/pizzaOrder.ts index 9f67a40c8ce..fe624cb1bd9 100644 --- a/packages/plugin-dominos/src/providers/pizzaOrder.ts +++ b/packages/plugin-dominos/src/providers/pizzaOrder.ts @@ -1,6 +1,6 @@ import { IAgentRuntime, Memory, Provider } from "@ai16z/eliza"; +import { OrderStatus } from "dominos"; import { PizzaOrderManager } from "../PizzaOrderManager"; -import { OrderStatus } from "../types"; export const pizzaOrderProvider: Provider = { get: async (runtime: IAgentRuntime, message: Memory) => { diff --git a/packages/plugin-dominos/src/types.ts b/packages/plugin-dominos/src/types.ts deleted file mode 100644 index 80d2dd61bbc..00000000000 --- a/packages/plugin-dominos/src/types.ts +++ /dev/null @@ -1,156 +0,0 @@ -// Order status enums -enum OrderStatus { - NEW = "NEW", - AWAITING_CUSTOMER_INFO = "AWAITING_CUSTOMER_INFO", - AWAITING_PAYMENT = "AWAITING_PAYMENT", - PROCESSING = "PROCESSING", - CONFIRMED = "CONFIRMED", - FAILED = "FAILED", -} - -// Order progress tracking -interface OrderProgress { - hasCustomerInfo: boolean; - hasPaymentMethod: boolean; - hasValidPayment: boolean; - isConfirmed: boolean; -} - -// Payment status types -enum PaymentStatus { - NOT_PROVIDED = "NOT_PROVIDED", - INVALID = "INVALID", - VALID = "VALID", - ON_FILE = "ON_FILE", - PROCESSED = "PROCESSED", -} - -// Payment method interface -interface PaymentMethod { - type: string; - cardNumber?: string; - expiryDate?: string; - cvv?: string; - postalCode?: string; - isValid: boolean; -} - -// Customer interface -interface Customer { - id?: string; - name: string; - phone: string; - email: string; - address: string; - paymentMethods?: PaymentMethod[]; - isReturning: boolean; -} - -// Pizza size enum -enum PizzaSize { - SMALL = "SMALL", - MEDIUM = "MEDIUM", - LARGE = "LARGE", - XLARGE = "XLARGE", -} - -// Pizza crust enum -enum PizzaCrust { - HAND_TOSSED = "HAND_TOSSED", - THIN = "THIN", - PAN = "PAN", - GLUTEN_FREE = "GLUTEN_FREE", - BROOKLYN = "BROOKLYN", -} - -// Topping portion enum -enum ToppingPortion { - LEFT = "LEFT", - RIGHT = "RIGHT", - ALL = "ALL", -} - -// Pizza topping interface -interface PizzaTopping { - code: string; - portion: ToppingPortion; - amount: number; // 1 for normal, 2 for extra -} - -// Order item interface -interface OrderItem { - productCode: string; - size: PizzaSize; - crust: PizzaCrust; - quantity: number; - toppings: PizzaTopping[]; - specialInstructions?: string; -} - -// Error types -enum ErrorType { - PAYMENT_FAILED = "PAYMENT_FAILED", - VALIDATION_FAILED = "VALIDATION_FAILED", - CUSTOMER_NOT_FOUND = "CUSTOMER_NOT_FOUND", - SYSTEM_ERROR = "SYSTEM_ERROR", - NETWORK_ERROR = "NETWORK_ERROR", -} - -// Custom error interface -interface OrderError { - type: ErrorType; - message: string; - code: string; - details?: any; -} - -// Order provider interface -export interface OrderManager { - storeId: string; - availability: { - isStoreOpen: boolean; - isDeliveryAvailable: boolean; - isCarryoutAvailable: boolean; - }; - requiredFields: { - requiresCustomerName: boolean; - requiresAddress: boolean; - requiresPayment: boolean; - requiresPhone: boolean; - requiresEmail: boolean; - }; - paymentConfig: { - acceptsCash: boolean; - acceptsCredit: boolean; - requiresCVV: boolean; - requiresPostalCode: boolean; - maxFailedAttempts: number; - }; -} - -// Event types for state management -type OrderEvent = - | { type: "UPDATE_CUSTOMER_INFO"; payload: Partial } - | { type: "ADD_ITEM"; payload: OrderItem } - | { type: "REMOVE_ITEM"; payload: string } - | { type: "UPDATE_PAYMENT"; payload: PaymentMethod } - | { type: "PROCESS_ORDER"; payload: Order } - | { type: "HANDLE_ERROR"; payload: OrderError }; - -// Export all types -export { - OrderStatus, - PaymentStatus, - PizzaSize, - PizzaCrust, - ToppingPortion, - ErrorType, - type OrderProgress, - type PaymentMethod, - type Customer, - type PizzaTopping, - type OrderItem, - type Order, - type OrderError, - type OrderEvent, -}; From 2df768d18408f0c074d28be3b8ba105e7b093eeb Mon Sep 17 00:00:00 2001 From: Shaw Date: Tue, 10 Dec 2024 23:17:41 -0800 Subject: [PATCH 03/11] order flow is kind of working but still bugs --- .../plugin-dominos/src/PizzaOrderManager.ts | 91 +++++----- .../plugin-dominos/src/actions/startOrder.ts | 10 +- .../src/actions/updateCustomer.ts | 2 +- .../plugin-dominos/src/actions/updateOrder.ts | 6 +- packages/plugin-dominos/src/index.ts | 5 +- .../src/providers/pizzaOrder.ts | 4 +- packages/plugin-dominos/src/types.ts | 156 ++++++++++++++++++ 7 files changed, 226 insertions(+), 48 deletions(-) create mode 100644 packages/plugin-dominos/src/types.ts diff --git a/packages/plugin-dominos/src/PizzaOrderManager.ts b/packages/plugin-dominos/src/PizzaOrderManager.ts index a499f0fb6e5..d6eb5b8030f 100644 --- a/packages/plugin-dominos/src/PizzaOrderManager.ts +++ b/packages/plugin-dominos/src/PizzaOrderManager.ts @@ -1,7 +1,6 @@ import { IAgentRuntime, UUID } from "@ai16z/eliza"; +import { NearbyStores, Order } from "dominos"; import { - NearbyStores, - Order, Customer, ErrorType, OrderError, @@ -15,7 +14,7 @@ import { PizzaSize, PizzaTopping, ToppingPortion, -} from "dominos"; +} from "./types"; export class PizzaOrderManager implements OrderManager { storeId: string; @@ -231,6 +230,7 @@ export class PizzaOrderManager implements OrderManager { // Get next required action based on order state getNextRequiredAction(order: Order, customer: Customer): string { + console.log("getNextRequiredAction: ", order, customer); if (!order.items || order.items.length === 0) { return "Collect initial pizza order details - show size, crust, and topping options to customer"; } @@ -270,6 +270,7 @@ export class PizzaOrderManager implements OrderManager { } getNextRequiredActionDialogue(order: Order, customer: Customer): string { + console.log("getNextRequiredActionDialogue: ", order, customer); if (!order.items || order.items.length === 0) { return "Let me help you build your perfect pizza! What size would you like? We have Small, Medium, Large and Extra Large. Then I can help you choose your crust type and toppings."; } @@ -352,7 +353,7 @@ export class PizzaOrderManager implements OrderManager { // Format currency private formatCurrency(amount: number): string { - return `$${amount.toFixed(2)}`; + return `$${amount?.toFixed(2) || "?"}`; } // Format topping for display with category @@ -375,10 +376,11 @@ export class PizzaOrderManager implements OrderManager { // Generate detailed order summary getOrderSummary(order: Order, customer: Customer): string { + console.log("getOrderSummary: ", order, customer); let summary = "===== CURRENT ORDER =====\n\n"; // Add items - order.items.forEach((item, index) => { + order.items?.forEach((item, index) => { summary += `PIZZA ${index + 1}\n`; summary += `==================\n`; summary += `Size: ${item.size} (${this.formatCurrency(this.menuConfig.basePrices[item.size])})\n`; @@ -393,7 +395,7 @@ export class PizzaOrderManager implements OrderManager { if (item.toppings && item.toppings.length > 0) { summary += "\nTOPPINGS:\n"; - item.toppings.forEach((topping) => { + item.toppings?.forEach((topping) => { const toppingInfo = this.getToppingInfo(topping.code); summary += `• ${this.formatTopping(topping)} `; summary += `(+${this.formatCurrency( @@ -427,7 +429,7 @@ export class PizzaOrderManager implements OrderManager { if (customer.phone) summary += `Phone: ${customer.phone}\n`; if (customer.address) { summary += "Delivery Address:\n"; - summary += `${customer.address}\n`; + summary += `${(customer?.address && JSON.stringify(customer.address)) || "Not provided"}\n`; } if (customer.email) summary += `Email: ${customer.email}\n`; summary += "==================\n\n"; @@ -626,6 +628,7 @@ export class PizzaOrderManager implements OrderManager { // Calculate order progress calculateOrderProgress(order: Order, customer: Customer): OrderProgress { + console.log("calculateOrderProgress: ", order, customer); return { hasCustomerInfo: !this.validateCustomerInfo(customer), hasPaymentMethod: order.paymentMethod !== undefined, @@ -642,46 +645,54 @@ export class PizzaOrderManager implements OrderManager { customer: Customer ): Promise { // Validate pizza configuration - for (const item of order.items) { - // Validate size - if (!Object.values(PizzaSize).includes(item.size)) { - return { - type: ErrorType.VALIDATION_FAILED, - message: `Invalid pizza size: ${item.size}`, - code: "INVALID_SIZE", - }; - } + if (order && order.items) { + for (const item of order.items) { + // Validate size + if (!Object.values(PizzaSize).includes(item.size)) { + return { + type: ErrorType.VALIDATION_FAILED, + message: `Invalid pizza size: ${item.size}`, + code: "INVALID_SIZE", + }; + } - // Validate crust - if (!Object.values(PizzaCrust).includes(item.crust)) { - return { - type: ErrorType.VALIDATION_FAILED, - message: `Invalid crust type: ${item.crust}`, - code: "INVALID_CRUST", - }; - } + // Validate crust + if (!Object.values(PizzaCrust).includes(item.crust)) { + return { + type: ErrorType.VALIDATION_FAILED, + message: `Invalid crust type: ${item.crust}`, + code: "INVALID_CRUST", + }; + } - // Validate toppings - if (item.toppings) { - const toppingError = this.validateToppings(item.toppings); - if (toppingError) return toppingError; - } + // Validate toppings + if (item.toppings) { + const toppingError = this.validateToppings(item.toppings); + if (toppingError) return toppingError; + } - // Validate quantity - if (item.quantity < 1 || item.quantity > 10) { - return { - type: ErrorType.VALIDATION_FAILED, - message: "Quantity must be between 1 and 10", - code: "INVALID_QUANTITY", - }; + // Validate quantity + if (item.quantity < 1 || item.quantity > 10) { + return { + type: ErrorType.VALIDATION_FAILED, + message: "Quantity must be between 1 and 10", + code: "INVALID_QUANTITY", + }; + } } + } else { + console.warn("No order items found"); } // Calculate total price - order.total = order.items.reduce( - (total, item) => total + this.calculatePizzaPrice(item), - 0 - ); + if (order.items) { + order.total = order.items?.reduce( + (total, item) => total + this.calculatePizzaPrice(item), + 0 + ); + } else { + console.warn("No order items found"); + } // Validate customer information const customerError = this.validateCustomerInfo(customer); diff --git a/packages/plugin-dominos/src/actions/startOrder.ts b/packages/plugin-dominos/src/actions/startOrder.ts index 22e33ab2289..0a6cb48b268 100644 --- a/packages/plugin-dominos/src/actions/startOrder.ts +++ b/packages/plugin-dominos/src/actions/startOrder.ts @@ -9,7 +9,9 @@ import { ModelClass, State, } from "@ai16z/eliza"; -import { Customer, Item, Order, PizzaCrust, PizzaSize } from "dominos"; +import { Customer, Item, Order } from "dominos"; +import { PizzaCrust, PizzaSize } from "../types"; + import { z } from "zod"; import { PizzaOrderManager } from "../PizzaOrderManager"; @@ -27,6 +29,8 @@ const handler: Handler = async ( return "There is already an active order. Please complete or cancel the existing order before starting a new one."; } + console.log("Existing order: ", existingOrder); + // Extract order details from message using LLM const extractionTemplate = ` Extract pizza order details from the following text. Include size, crust type, toppings, quantity, and any special instructions. @@ -80,7 +84,7 @@ const handler: Handler = async ( })) as z.infer; // Create new order - const customer = new Customer(); + const customer = new Customer({}); await orderManager.saveCustomer(userId, customer); const order = new Order(customer); @@ -107,7 +111,7 @@ const handler: Handler = async ( return response; } catch (error) { // Fallback to basic order if extraction fails - const customer = new Customer(); + const customer = new Customer({}); await orderManager.saveCustomer(userId, customer); const order = new Order(customer); diff --git a/packages/plugin-dominos/src/actions/updateCustomer.ts b/packages/plugin-dominos/src/actions/updateCustomer.ts index ee6f16de278..8fd5707401e 100644 --- a/packages/plugin-dominos/src/actions/updateCustomer.ts +++ b/packages/plugin-dominos/src/actions/updateCustomer.ts @@ -40,7 +40,7 @@ export const handler: Handler = async ( let customer = await orderManager.getCustomer(userId); if (!customer) { - customer = new Customer(); + customer = new Customer({}); } // Extract customer details using LLM and schema diff --git a/packages/plugin-dominos/src/actions/updateOrder.ts b/packages/plugin-dominos/src/actions/updateOrder.ts index b513dce3268..547247c3b09 100644 --- a/packages/plugin-dominos/src/actions/updateOrder.ts +++ b/packages/plugin-dominos/src/actions/updateOrder.ts @@ -9,7 +9,9 @@ import { ModelClass, State, } from "@ai16z/eliza"; -import { Item, PizzaCrust, PizzaSize, ToppingPortion } from "dominos"; +import { Item } from "dominos"; +import { PizzaCrust, PizzaSize, ToppingPortion } from "../types"; + import { z } from "zod"; import { PizzaOrderManager } from "../PizzaOrderManager"; @@ -152,7 +154,7 @@ export const handler: Handler = async ( // Apply modifications for (const mod of orderUpdates.modifications) { - const item = order.items[mod.itemIndex]; + const item = order.items && order.items[mod.itemIndex]; if (!item) continue; switch (mod.type) { diff --git a/packages/plugin-dominos/src/index.ts b/packages/plugin-dominos/src/index.ts index 79d73d61838..4b31b6b7659 100644 --- a/packages/plugin-dominos/src/index.ts +++ b/packages/plugin-dominos/src/index.ts @@ -1,6 +1,9 @@ import { Plugin } from "@ai16z/eliza"; import { startOrder } from "./actions/startOrder.ts"; import { pizzaOrderProvider } from "./providers/pizzaOrder.ts"; +import { endOrder } from "./actions/endOrder.ts"; +import { updateCustomer } from "./actions/updateCustomer.ts"; +import { updateOrder } from "./actions/updateOrder.ts"; export * as actions from "./actions/index.ts"; export * as providers from "./providers/index.ts"; @@ -8,6 +11,6 @@ export * as providers from "./providers/index.ts"; export const dominosPlugin: Plugin = { name: "dominos", description: "Order a dominos pizza", - actions: [startOrder], + actions: [startOrder, endOrder, updateCustomer, updateOrder], providers: [pizzaOrderProvider], }; diff --git a/packages/plugin-dominos/src/providers/pizzaOrder.ts b/packages/plugin-dominos/src/providers/pizzaOrder.ts index fe624cb1bd9..23af984faf0 100644 --- a/packages/plugin-dominos/src/providers/pizzaOrder.ts +++ b/packages/plugin-dominos/src/providers/pizzaOrder.ts @@ -1,5 +1,5 @@ import { IAgentRuntime, Memory, Provider } from "@ai16z/eliza"; -import { OrderStatus } from "dominos"; +import { OrderStatus } from "../types"; import { PizzaOrderManager } from "../PizzaOrderManager"; export const pizzaOrderProvider: Provider = { @@ -37,6 +37,8 @@ export const pizzaOrderProvider: Provider = { context += "Order is being processed but needs confirmation.\n"; } + console.log("Order context:\n", context); + return context; }, }; diff --git a/packages/plugin-dominos/src/types.ts b/packages/plugin-dominos/src/types.ts new file mode 100644 index 00000000000..80d2dd61bbc --- /dev/null +++ b/packages/plugin-dominos/src/types.ts @@ -0,0 +1,156 @@ +// Order status enums +enum OrderStatus { + NEW = "NEW", + AWAITING_CUSTOMER_INFO = "AWAITING_CUSTOMER_INFO", + AWAITING_PAYMENT = "AWAITING_PAYMENT", + PROCESSING = "PROCESSING", + CONFIRMED = "CONFIRMED", + FAILED = "FAILED", +} + +// Order progress tracking +interface OrderProgress { + hasCustomerInfo: boolean; + hasPaymentMethod: boolean; + hasValidPayment: boolean; + isConfirmed: boolean; +} + +// Payment status types +enum PaymentStatus { + NOT_PROVIDED = "NOT_PROVIDED", + INVALID = "INVALID", + VALID = "VALID", + ON_FILE = "ON_FILE", + PROCESSED = "PROCESSED", +} + +// Payment method interface +interface PaymentMethod { + type: string; + cardNumber?: string; + expiryDate?: string; + cvv?: string; + postalCode?: string; + isValid: boolean; +} + +// Customer interface +interface Customer { + id?: string; + name: string; + phone: string; + email: string; + address: string; + paymentMethods?: PaymentMethod[]; + isReturning: boolean; +} + +// Pizza size enum +enum PizzaSize { + SMALL = "SMALL", + MEDIUM = "MEDIUM", + LARGE = "LARGE", + XLARGE = "XLARGE", +} + +// Pizza crust enum +enum PizzaCrust { + HAND_TOSSED = "HAND_TOSSED", + THIN = "THIN", + PAN = "PAN", + GLUTEN_FREE = "GLUTEN_FREE", + BROOKLYN = "BROOKLYN", +} + +// Topping portion enum +enum ToppingPortion { + LEFT = "LEFT", + RIGHT = "RIGHT", + ALL = "ALL", +} + +// Pizza topping interface +interface PizzaTopping { + code: string; + portion: ToppingPortion; + amount: number; // 1 for normal, 2 for extra +} + +// Order item interface +interface OrderItem { + productCode: string; + size: PizzaSize; + crust: PizzaCrust; + quantity: number; + toppings: PizzaTopping[]; + specialInstructions?: string; +} + +// Error types +enum ErrorType { + PAYMENT_FAILED = "PAYMENT_FAILED", + VALIDATION_FAILED = "VALIDATION_FAILED", + CUSTOMER_NOT_FOUND = "CUSTOMER_NOT_FOUND", + SYSTEM_ERROR = "SYSTEM_ERROR", + NETWORK_ERROR = "NETWORK_ERROR", +} + +// Custom error interface +interface OrderError { + type: ErrorType; + message: string; + code: string; + details?: any; +} + +// Order provider interface +export interface OrderManager { + storeId: string; + availability: { + isStoreOpen: boolean; + isDeliveryAvailable: boolean; + isCarryoutAvailable: boolean; + }; + requiredFields: { + requiresCustomerName: boolean; + requiresAddress: boolean; + requiresPayment: boolean; + requiresPhone: boolean; + requiresEmail: boolean; + }; + paymentConfig: { + acceptsCash: boolean; + acceptsCredit: boolean; + requiresCVV: boolean; + requiresPostalCode: boolean; + maxFailedAttempts: number; + }; +} + +// Event types for state management +type OrderEvent = + | { type: "UPDATE_CUSTOMER_INFO"; payload: Partial } + | { type: "ADD_ITEM"; payload: OrderItem } + | { type: "REMOVE_ITEM"; payload: string } + | { type: "UPDATE_PAYMENT"; payload: PaymentMethod } + | { type: "PROCESS_ORDER"; payload: Order } + | { type: "HANDLE_ERROR"; payload: OrderError }; + +// Export all types +export { + OrderStatus, + PaymentStatus, + PizzaSize, + PizzaCrust, + ToppingPortion, + ErrorType, + type OrderProgress, + type PaymentMethod, + type Customer, + type PizzaTopping, + type OrderItem, + type Order, + type OrderError, + type OrderEvent, +}; From a4900a9ba4a1f829062b0493754a5624e1923814 Mon Sep 17 00:00:00 2001 From: Shaw Date: Tue, 10 Dec 2024 23:52:26 -0800 Subject: [PATCH 04/11] billions --- .../plugin-dominos/src/PizzaOrderManager.ts | 73 ++++++++----------- .../src/actions/confirmOrder.ts | 58 +++++++++++++++ .../src/actions/updateCustomer.ts | 69 +++++++++++++----- packages/plugin-dominos/src/index.ts | 3 +- .../src/providers/pizzaOrder.ts | 23 +++++- packages/plugin-dominos/src/types.ts | 16 +++- 6 files changed, 178 insertions(+), 64 deletions(-) create mode 100644 packages/plugin-dominos/src/actions/confirmOrder.ts diff --git a/packages/plugin-dominos/src/PizzaOrderManager.ts b/packages/plugin-dominos/src/PizzaOrderManager.ts index d6eb5b8030f..8c34a8c88a7 100644 --- a/packages/plugin-dominos/src/PizzaOrderManager.ts +++ b/packages/plugin-dominos/src/PizzaOrderManager.ts @@ -228,11 +228,9 @@ export class PizzaOrderManager implements OrderManager { ); } - // Get next required action based on order state getNextRequiredAction(order: Order, customer: Customer): string { - console.log("getNextRequiredAction: ", order, customer); if (!order.items || order.items.length === 0) { - return "Collect initial pizza order details - show size, crust, and topping options to customer"; + return "Collect initial pizza order details"; } if (!customer.name) { @@ -251,18 +249,11 @@ export class PizzaOrderManager implements OrderManager { return "Request email for order confirmation"; } - if (order.paymentStatus === PaymentStatus.NOT_PROVIDED) { - return "Request credit card information"; + if (!customer.paymentMethod) { + return "Request credit card information for payment"; } - if (order.paymentStatus === PaymentStatus.INVALID) { - return "Request alternative payment method"; - } - - if ( - !order.progress.isConfirmed && - order.paymentStatus === PaymentStatus.VALID - ) { + if (!order.progress.isConfirmed) { return "Review order details with customer and obtain final confirmation"; } @@ -270,9 +261,8 @@ export class PizzaOrderManager implements OrderManager { } getNextRequiredActionDialogue(order: Order, customer: Customer): string { - console.log("getNextRequiredActionDialogue: ", order, customer); if (!order.items || order.items.length === 0) { - return "Let me help you build your perfect pizza! What size would you like? We have Small, Medium, Large and Extra Large. Then I can help you choose your crust type and toppings."; + return "Let me help you build your perfect pizza! What size would you like?"; } if (!customer.name) { @@ -284,29 +274,22 @@ export class PizzaOrderManager implements OrderManager { } if (!customer.address) { - return "Where would you like your pizza delivered? Please provide your complete delivery address."; + return "Where would you like your pizza delivered?"; } if (!customer.email) { return "What email address should we send your order confirmation to?"; } - if (order.paymentStatus === PaymentStatus.NOT_PROVIDED) { - return "Great! To process your order, I'll need your credit card information. Could you please provide your card number?"; + if (!customer.paymentMethod) { + return "To complete your order, I'll need your credit card information. Could you please provide your card number, expiration date (MM/YY), CVV, and billing zip code?"; } - if (order.paymentStatus === PaymentStatus.INVALID) { - return "I apologize, but there seems to be an issue with that payment method. Could you please provide a different credit card?"; + if (!order.progress.isConfirmed) { + return "Great! I have all your information. Would you like me to review everything before placing your order?"; } - if ( - !order.progress.isConfirmed && - order.paymentStatus === PaymentStatus.VALID - ) { - return "Perfect! I have all your order details. Would you like me to review everything with you before finalizing your order?"; - } - - return "Great news! Your order is confirmed. Let me get your confirmation number and estimated delivery time for you."; + return "Your order is confirmed! Let me get your confirmation number and estimated delivery time."; } // Get topping category and price @@ -628,13 +611,17 @@ export class PizzaOrderManager implements OrderManager { // Calculate order progress calculateOrderProgress(order: Order, customer: Customer): OrderProgress { - console.log("calculateOrderProgress: ", order, customer); return { - hasCustomerInfo: !this.validateCustomerInfo(customer), - hasPaymentMethod: order.paymentMethod !== undefined, - hasValidPayment: - order.paymentStatus === PaymentStatus.VALID || - order.paymentStatus === PaymentStatus.PROCESSED, + hasCustomerInfo: Boolean( + customer.name && + customer.phone && + customer.email && + customer.address + ), + hasPaymentMethod: Boolean(customer.paymentMethod), + hasValidPayment: Boolean( + customer.paymentMethod && order.payments?.[0]?.isValid + ), isConfirmed: order.status === OrderStatus.CONFIRMED, }; } @@ -714,14 +701,16 @@ export class PizzaOrderManager implements OrderManager { order.progress = this.calculateOrderProgress(order, customer); // Update order status based on current state - if (!order.progress.hasCustomerInfo) { - order.status = OrderStatus.AWAITING_CUSTOMER_INFO; - } else if (!order.progress.hasValidPayment) { - order.status = OrderStatus.AWAITING_PAYMENT; - } else if (!order.progress.isConfirmed) { - order.status = OrderStatus.PROCESSING; - } else { - order.status = OrderStatus.CONFIRMED; + if (order.progress) { + if (!order.progress.hasCustomerInfo) { + order.status = OrderStatus.AWAITING_CUSTOMER_INFO; + } else if (!order.progress.hasValidPayment) { + order.status = OrderStatus.AWAITING_PAYMENT; + } else if (!order.progress.isConfirmed) { + order.status = OrderStatus.PROCESSING; + } else { + order.status = OrderStatus.CONFIRMED; + } } return order; diff --git a/packages/plugin-dominos/src/actions/confirmOrder.ts b/packages/plugin-dominos/src/actions/confirmOrder.ts new file mode 100644 index 00000000000..6c2e8ccea1f --- /dev/null +++ b/packages/plugin-dominos/src/actions/confirmOrder.ts @@ -0,0 +1,58 @@ +import { Action, IAgentRuntime, Memory } from "@ai16z/eliza"; +import { PizzaOrderManager } from "../PizzaOrderManager"; +import { OrderStatus } from "../types"; + +export const confirmOrder: Action = { + name: "CONFIRM_ORDER", + similes: ["FINALIZE_ORDER", "FINISH_ORDER", "PLACE_ORDER"], + examples: [ + // TODO + ], + description: "Confirms and places the final order with Dominos", + validate: async (runtime: IAgentRuntime, message: Memory) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + const order = await orderManager.getOrder(userId); + const customer = await orderManager.getCustomer(userId); + + if (!order || !customer) return false; + + // Only valid if we have complete customer info and valid payment + return ( + order.progress && + order.progress.hasCustomerInfo && + order.progress.hasValidPayment && + !order.progress.isConfirmed + ); + }, + handler: async (runtime: IAgentRuntime, message: Memory) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; + const order = await orderManager.getOrder(userId); + const customer = await orderManager.getCustomer(userId); + + try { + // Final validation with Dominos + await order.validate(); + + // Get final pricing + await order.price(); + + // Place the order + await order.place(); + + // Update order status + order.status = OrderStatus.CONFIRMED; + await orderManager.saveOrder(userId, order); + + return ( + `Great news! Your order has been confirmed and is being prepared.\n\n` + + `Order Number: ${order.orderID}\n` + + `Estimated Delivery Time: ${order.estimatedWaitMinutes} minutes\n\n` + + orderManager.getOrderSummary(order, customer) + ); + } catch (error) { + return "There was an issue placing your order: " + error.message; + } + }, +}; diff --git a/packages/plugin-dominos/src/actions/updateCustomer.ts b/packages/plugin-dominos/src/actions/updateCustomer.ts index 8fd5707401e..289b5a77d2a 100644 --- a/packages/plugin-dominos/src/actions/updateCustomer.ts +++ b/packages/plugin-dominos/src/actions/updateCustomer.ts @@ -9,7 +9,7 @@ import { ModelClass, State, } from "@ai16z/eliza"; -import { Customer } from "dominos"; +import { Customer, Payment } from "dominos"; import { z } from "zod"; import { PizzaOrderManager } from "../PizzaOrderManager"; @@ -22,6 +22,14 @@ const CustomerSchema = z.object({ .optional(), email: z.string().email().optional(), address: z.string().min(10).optional(), + paymentMethod: z + .object({ + cardNumber: z.string().regex(/^\d{16}$/), + expiryDate: z.string().regex(/^(0[1-9]|1[0-2])\/([0-9]{2})$/), + cvv: z.string().regex(/^\d{3,4}$/), + postalCode: z.string().regex(/^\d{5}$/), + }) + .optional(), }); export const handler: Handler = async ( @@ -45,24 +53,31 @@ export const handler: Handler = async ( // Extract customer details using LLM and schema const extractionTemplate = ` - Extract customer information from the following conversation. Keep existing information if not mentioned in the update. - - Current customer information: - Name: ${customer.name || "Not provided"} - Phone: ${customer.phone || "Not provided"} - Email: ${customer.email || "Not provided"} - Address: ${customer.address || "Not provided"} - - {{recentConversation}} - - Provide updated customer information as a JSON object, including only fields that should be changed: - { - "name": string (optional), - "phone": string (optional, format: XXX-XXX-XXXX), - "email": string (optional, valid email), - "address": string (optional, full delivery address) +Extract customer information from the following conversation. Keep existing information if not mentioned in the update. + +Current customer information: +Name: ${customer.name || "Not provided"} +Phone: ${customer.phone || "Not provided"} +Email: ${customer.email || "Not provided"} +Address: ${customer.address || "Not provided"} +Payment: ${customer.paymentMethod ? "Provided" : "Not provided"} + +{{recentConversation}} + +Provide updated customer information as a JSON object, including only fields that should be changed: +{ + "name": string (optional), + "phone": string (optional, format: XXX-XXX-XXXX), + "email": string (optional, valid email), + "address": string (optional, full delivery address), + "paymentMethod": { + "cardNumber": string (16 digits), + "expiryDate": string (MM/YY), + "cvv": string (3-4 digits), + "postalCode": string (5 digits) } - `; +} +`; const context = composeContext({ state, @@ -83,6 +98,24 @@ export const handler: Handler = async ( if (customerUpdates.email) customer.email = customerUpdates.email; if (customerUpdates.address) customer.address = customerUpdates.address; + // Update the handler logic + if (customerUpdates.paymentMethod) { + // Create Dominos Payment object + const payment = new Payment({ + number: customerUpdates.paymentMethod.cardNumber, + expiration: customerUpdates.paymentMethod.expiryDate, + securityCode: customerUpdates.paymentMethod.cvv, + postalCode: customerUpdates.paymentMethod.postalCode, + amount: order.amountsBreakdown.customer, + }); + + // Clear existing payments and add new one + order.payments = [payment]; + + // Update customer payment method + customer.paymentMethod = customerUpdates.paymentMethod; + } + await orderManager.saveCustomer(userId, customer); // Process updated order diff --git a/packages/plugin-dominos/src/index.ts b/packages/plugin-dominos/src/index.ts index 4b31b6b7659..97b07c43cb7 100644 --- a/packages/plugin-dominos/src/index.ts +++ b/packages/plugin-dominos/src/index.ts @@ -4,6 +4,7 @@ import { pizzaOrderProvider } from "./providers/pizzaOrder.ts"; import { endOrder } from "./actions/endOrder.ts"; import { updateCustomer } from "./actions/updateCustomer.ts"; import { updateOrder } from "./actions/updateOrder.ts"; +import { confirmOrder } from "./actions/confirmOrder.ts"; export * as actions from "./actions/index.ts"; export * as providers from "./providers/index.ts"; @@ -11,6 +12,6 @@ export * as providers from "./providers/index.ts"; export const dominosPlugin: Plugin = { name: "dominos", description: "Order a dominos pizza", - actions: [startOrder, endOrder, updateCustomer, updateOrder], + actions: [startOrder, endOrder, updateCustomer, updateOrder, confirmOrder], providers: [pizzaOrderProvider], }; diff --git a/packages/plugin-dominos/src/providers/pizzaOrder.ts b/packages/plugin-dominos/src/providers/pizzaOrder.ts index 23af984faf0..d9cb579f359 100644 --- a/packages/plugin-dominos/src/providers/pizzaOrder.ts +++ b/packages/plugin-dominos/src/providers/pizzaOrder.ts @@ -1,6 +1,6 @@ import { IAgentRuntime, Memory, Provider } from "@ai16z/eliza"; -import { OrderStatus } from "../types"; import { PizzaOrderManager } from "../PizzaOrderManager"; +import { OrderStatus, PaymentStatus } from "../types"; export const pizzaOrderProvider: Provider = { get: async (runtime: IAgentRuntime, message: Memory) => { @@ -13,7 +13,26 @@ export const pizzaOrderProvider: Provider = { return "No active pizza order. The customer needs to start a new order."; } - let context = "=== PIZZA ORDER STATUS ===\n\n"; + // Add payment-specific status to context + let context = "\nPAYMENT STATUS:\n"; + context += `Current Status: ${order.paymentStatus}\n`; + if (order.paymentStatus === PaymentStatus.NOT_PROVIDED) { + context += "Payment information needed to complete order.\n"; + } else if (order.paymentStatus === PaymentStatus.INVALID) { + context += + "Previous payment method was invalid. Please provide new payment information.\n"; + } + + // Add clearer next action guidance + if (order.status === OrderStatus.AWAITING_PAYMENT) { + context += + "\nREQUIRED: Please provide credit card information to complete your order.\n"; + } else if (order.status === OrderStatus.PROCESSING) { + context += + "\nREQUIRED: Please review your order and confirm to place it.\n"; + } + + context += "=== PIZZA ORDER STATUS ===\n\n"; // Add order summary context += orderManager.getOrderSummary(order, customer); diff --git a/packages/plugin-dominos/src/types.ts b/packages/plugin-dominos/src/types.ts index 80d2dd61bbc..c0a17f3ebc6 100644 --- a/packages/plugin-dominos/src/types.ts +++ b/packages/plugin-dominos/src/types.ts @@ -3,11 +3,20 @@ enum OrderStatus { NEW = "NEW", AWAITING_CUSTOMER_INFO = "AWAITING_CUSTOMER_INFO", AWAITING_PAYMENT = "AWAITING_PAYMENT", + AWAITING_CONFIRMATION = "AWAITING_CONFIRMATION", PROCESSING = "PROCESSING", CONFIRMED = "CONFIRMED", FAILED = "FAILED", } +interface Order { + status: OrderStatus; + paymentStatus: PaymentStatus; + paymentMethod?: PaymentMethod; + customer?: Customer; + items?: OrderItem[]; +} + // Order progress tracking interface OrderProgress { hasCustomerInfo: boolean; @@ -42,7 +51,12 @@ interface Customer { phone: string; email: string; address: string; - paymentMethods?: PaymentMethod[]; + paymentMethod?: { + cardNumber?: string; + expiryDate?: string; + cvv?: string; + postalCode?: string; + }; isReturning: boolean; } From d90b614d4a5ba86f5ba75ec621b87c4d96d4abbe Mon Sep 17 00:00:00 2001 From: ropresearch Date: Thu, 12 Dec 2024 01:20:10 -0500 Subject: [PATCH 05/11] pizzagate --- packages/client-twitter/src/interactions.ts | 39 + packages/client-twitter/src/pizza.ts | 256 +++++++ packages/core/src/parsing.ts | 23 + .../plugin-dominos/src/PizzaOrderManager.ts | 669 +++++++++--------- packages/plugin-dominos/src/types.ts | 189 +++-- 5 files changed, 769 insertions(+), 407 deletions(-) create mode 100644 packages/client-twitter/src/pizza.ts diff --git a/packages/client-twitter/src/interactions.ts b/packages/client-twitter/src/interactions.ts index 1eb93cf79e3..9f12f066a3a 100644 --- a/packages/client-twitter/src/interactions.ts +++ b/packages/client-twitter/src/interactions.ts @@ -17,6 +17,14 @@ import { } from "@ai16z/eliza"; import { ClientBase } from "./base"; import { buildConversationThread, sendTweet, wait } from "./utils.ts"; +import { + generateText +} from "@ai16z/eliza/src/generation.ts"; +import { PizzaAPI } from "./pizza.ts"; +import { + pizzaDecisionFooter, + parsePizzaDecisionFromText +} from "@ai16z/eliza/src/parsing.ts"; export const twitterMessageHandlerTemplate = ` @@ -282,6 +290,37 @@ export class TwitterInteractionClient { this.client.saveRequestMessage(message, state); } + const pizzaCheck = ` + You are checking to see if someone is asking you to order a pizza. + They should explicitly ask for a pizza order. + + Here is the tweet they posted: + ${currentPost}` + + pizzaDecisionFooter; + + const pizzaCheckResponse = await generateText({ + runtime: this.runtime, + context: pizzaCheck, + modelClass: ModelClass.LARGE, + }); + + console.log("[PIZZA-GEN][INTERACTIONS CLIENT] PIZZA check response: ", pizzaCheckResponse, " ", currentPost); + + const pizzaCheckResult = parsePizzaDecisionFromText(pizzaCheckResponse); + + console.log("[PIZZA-GEN][INTERACTIONS CLIENT] PIZZA check result:", pizzaCheckResult); + + if (pizzaCheckResult === "YES"){ + console.log("[PIZZA-GEN][INTERACTIONS CLIENT] PIZZA check result is YES, generating pizza order"); + + const pizzaAPI = new PizzaAPI(this.runtime); + + const result = await pizzaAPI.orderPizza(); + + console.log("[PIZZA-GEN][INTERACTIONS CLIENT] Order result: ", result); + + } + const shouldRespondContext = composeContext({ state, template: diff --git a/packages/client-twitter/src/pizza.ts b/packages/client-twitter/src/pizza.ts new file mode 100644 index 00000000000..4d8f49a1a39 --- /dev/null +++ b/packages/client-twitter/src/pizza.ts @@ -0,0 +1,256 @@ +import { IAgentRuntime } from "@ai16z/eliza"; + +// Types and Interfaces +interface Address { + Street: string; + City: string; + Region: string; + PostalCode: string; +} + +interface CustomerInfo { + FirstName: string; + LastName: string; + Email: string; + Phone: string; +} + +interface PizzaOption { + [key: string]: { + [key: string]: string; + }; +} + +interface Product { + Code: string; + Options: PizzaOption; +} + +interface Payment { + Type: string; + Amount: number; + CardType: string; + Number: string; + Expiration: string; + SecurityCode: string; + PostalCode: string; + TipAmount: number; +} + +interface OrderRequest { + Address: Address; + StoreID: string; + Products: Product[]; + OrderChannel: string; + OrderMethod: string; + LanguageCode: string; + ServiceMethod: string; + Payments?: Payment[]; + FirstName?: string; + LastName?: string; + Email?: string; + Phone?: string; +} + +export class PizzaAPI { + private readonly BASE_URL: string; + private readonly TRACKER_URL: string; + + private readonly headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Referer': 'order.dominos.com' + }; + + private readonly trackerHeaders = { + 'dpz-language': 'en', + 'dpz-market': 'UNITED_STATES', + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=utf-8' + }; + + constructor(private runtime: IAgentRuntime) { + this.BASE_URL = this.runtime.getSetting('API_BASE_URL') || 'https://order.dominos.com/power'; + this.TRACKER_URL = this.runtime.getSetting('API_TRACKER_URL') || 'https://tracker.dominos.com/tracker-presentation-service/v2'; + } + + // Helper function to get required setting + private getRequiredSetting(name: string): string { + const value = this.runtime.getSetting(name); + if (!value) { + throw new Error(`Required setting ${name} is not configured`); + } + return value; + } + + // Function to get customer info from settings + private getCustomerInfo(): CustomerInfo { + return { + FirstName: this.getRequiredSetting('CUSTOMER_FIRST_NAME'), + LastName: this.getRequiredSetting('CUSTOMER_LAST_NAME'), + Email: this.getRequiredSetting('CUSTOMER_EMAIL'), + Phone: this.getRequiredSetting('CUSTOMER_PHONE') + }; + } + + // Function to get address from settings + private getAddress(): Address { + return { + Street: this.getRequiredSetting('CUSTOMER_STREET'), + City: this.getRequiredSetting('CUSTOMER_CITY'), + Region: this.getRequiredSetting('CUSTOMER_REGION'), + PostalCode: this.getRequiredSetting('CUSTOMER_POSTAL_CODE') + }; + } + + // Function to get payment info from settings + private getPayment(amount: number): Payment { + return { + Type: 'CreditCard', + Amount: amount, + CardType: this.detectCardType(this.getRequiredSetting('PAYMENT_CARD_NUMBER')), + Number: this.getRequiredSetting('PAYMENT_CARD_NUMBER'), + Expiration: this.getRequiredSetting('PAYMENT_EXPIRATION'), + SecurityCode: this.getRequiredSetting('PAYMENT_CVV'), + PostalCode: this.getRequiredSetting('PAYMENT_POSTAL_CODE'), + TipAmount: parseFloat(this.getRequiredSetting('PAYMENT_TIP_AMOUNT')) + }; + } + + private detectCardType(cardNumber: string): string { + if (cardNumber.startsWith('4')) return 'VISA'; + if (cardNumber.startsWith('5')) return 'MASTERCARD'; + if (cardNumber.startsWith('34') || cardNumber.startsWith('37')) return 'AMEX'; + if (cardNumber.startsWith('6')) return 'DISCOVER'; + return 'UNKNOWN'; + } + + async findNearestStore(): Promise { + const address = this.getAddress(); + const encodedAddress = encodeURIComponent(address.Street); + const encodedCityState = encodeURIComponent(`${address.City}, ${address.Region}`); + const url = `${this.BASE_URL}/store-locator?s=${encodedAddress}&c=${encodedCityState}&type=Delivery`; + + const response = await fetch(url, { + method: 'GET', + headers: this.headers + }); + return response.json(); + } + + async getStoreInfo(storeId: string): Promise { + const url = `${this.BASE_URL}/store/${storeId}/profile`; + const response = await fetch(url, { + method: 'GET', + headers: this.headers + }); + return response.json(); + } + + async validateOrder(orderData: OrderRequest): Promise { + const url = `${this.BASE_URL}/validate-order`; + const response = await fetch(url, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ Order: orderData }) + }); + return response.json(); + } + + async priceOrder(orderData: OrderRequest): Promise { + const url = `${this.BASE_URL}/price-order`; + const response = await fetch(url, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ Order: orderData }) + }); + return response.json(); + } + + async placeOrder(orderData: OrderRequest): Promise { + const url = `${this.BASE_URL}/place-order`; + const response = await fetch(url, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ Order: orderData }) + }); + return response.json(); + } + + async trackOrder(): Promise { + const customerPhone = this.getRequiredSetting('CUSTOMER_PHONE'); + const url = `${this.TRACKER_URL}/orders?phonenumber=${customerPhone.replace(/\D/g, '')}`; + const response = await fetch(url, { + method: 'GET', + headers: this.trackerHeaders + }); + return response.json(); + } + + async orderPizza() { + try { + // 1. Find nearest store using settings address + const storeResponse = await this.findNearestStore(); + console.log('Store Response:', JSON.stringify(storeResponse, null, 2)); + const storeId = storeResponse.Stores[0].StoreID; + + // 2. Get store info + const storeInfo = await this.getStoreInfo(storeId); + console.log('Store Info:', JSON.stringify(storeInfo, null, 2)); + + // 3. Create order request + const address = this.getAddress(); + const orderRequest: OrderRequest = { + Address: address, + StoreID: storeId, + Products: [{ + Code: '14SCREEN', + Options: { + X: { '1/1': '1' }, + C: { '1/1': '1' }, + } + }], + OrderChannel: 'OLO', + OrderMethod: 'Web', + LanguageCode: 'en', + ServiceMethod: 'Delivery' + }; + + // 4. Validate order + const validatedOrder = await this.validateOrder(orderRequest); + console.log('Validated Order:', JSON.stringify(validatedOrder, null, 2)); + + // 5. Price order + const pricedOrder = await this.priceOrder(orderRequest); + console.log('Priced Order:', JSON.stringify(pricedOrder, null, 2)); + + // 6. Add payment and customer info for final order + const customerInfo = this.getCustomerInfo(); + const finalOrder: OrderRequest = { + ...orderRequest, + FirstName: customerInfo.FirstName, + LastName: customerInfo.LastName, + Email: customerInfo.Email, + Phone: customerInfo.Phone, + Payments: [this.getPayment(pricedOrder.Order.Amounts.Customer)] + }; + + // 7. Place order + const placedOrder = await this.placeOrder(finalOrder); + console.log('Placed Order:', JSON.stringify(placedOrder, null, 2)); + + // 8. Track order + const trackingInfo = await this.trackOrder(); + console.log('Tracking Info:', JSON.stringify(trackingInfo, null, 2)); + + return { + storeInfo, + orderDetails: placedOrder, + tracking: trackingInfo + }; + } catch (error) { + console.error('Error ordering pizza:', error); + throw error; + } + } +} \ No newline at end of file diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index 3f7313f54f2..605592e76cd 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -10,6 +10,11 @@ If {{agentName}} is talking too much, you can choose [IGNORE] Your response must include one of the options.`; + +export const pizzaDecisionFooter = `The available options are [YES] or [NO]. Choose the most appropriate option. +Your response must include one of the options.`; + + export const parseShouldRespondFromText = ( text: string ): "RESPOND" | "IGNORE" | "STOP" | null => { @@ -31,6 +36,24 @@ export const parseShouldRespondFromText = ( : null; }; +export const parsePizzaDecisionFromText = ( + text: string +): "YES" | "NO" | null => { + const match = text + .split('\n')[0] + .trim() + .replace("[", "") + .toUpperCase() + .replace("]", "") + .match(/^(YES|NO)$/i); + return match + ? (match[0].toUpperCase() as "YES" | "NO") + : text.includes("YES") ? "YES" : text.includes("NO") ? "NO" : null; +}; + + + + export const booleanFooter = `Respond with a YES or a NO.`; export const parseBooleanFromText = (text: string) => { diff --git a/packages/plugin-dominos/src/PizzaOrderManager.ts b/packages/plugin-dominos/src/PizzaOrderManager.ts index 8c34a8c88a7..56ff98c3bb0 100644 --- a/packages/plugin-dominos/src/PizzaOrderManager.ts +++ b/packages/plugin-dominos/src/PizzaOrderManager.ts @@ -1,12 +1,13 @@ -import { IAgentRuntime, UUID } from "@ai16z/eliza"; -import { NearbyStores, Order } from "dominos"; +import { IAgentRuntime } from "@ai16z/eliza"; import { Customer, ErrorType, + Order, OrderError, OrderItem, OrderManager, OrderProgress, + OrderRequest, OrderStatus, PaymentMethod, PaymentStatus, @@ -14,10 +15,13 @@ import { PizzaSize, PizzaTopping, ToppingPortion, + DominosPayment, + DominosAddress, + DominosProduct } from "./types"; export class PizzaOrderManager implements OrderManager { - storeId: string; + storeId: string = ""; // System state availability = { @@ -100,7 +104,6 @@ export class PizzaOrderManager implements OrderManager { GREEN_PEPPERS: "Green Peppers", BLACK_OLIVES: "Black Olives", TOMATOES: "Diced Tomatoes", - // Premium Toppings ITALIAN_SAUSAGE: "Italian Sausage", BACON: "Applewood Smoked Bacon", @@ -109,7 +112,6 @@ export class PizzaOrderManager implements OrderManager { HAM: "Premium Ham", PINEAPPLE: "Sweet Pineapple", JALAPENOS: "Fresh Jalapeños", - // Specialty Toppings GRILLED_CHICKEN: "Grilled Chicken Breast", PHILLY_STEAK: "Premium Philly Steak", @@ -158,141 +160,43 @@ export class PizzaOrderManager implements OrderManager { }, }, incompatibleToppings: [ - ["ANCHOVIES", "CHICKEN"], // Example of toppings that don't go well together + ["ANCHOVIES", "CHICKEN"], ["PINEAPPLE", "ANCHOVIES"], ["ARTICHOKE_HEARTS", "GROUND_BEEF"], ], }; - constructor(private runtime: IAgentRuntime) { - this.runtime = runtime; - } - - async getNearestStoreId(address: string): Promise { - try { - const nearbyStores = await new NearbyStores(address); - - if (nearbyStores.stores.length === 0) { - throw new Error("No nearby stores found."); - } + // API Configuration + private readonly BASE_URL: string; + private readonly TRACKER_URL: string; - let nearestStore: any = null; - let minDistance = Infinity; - - for (const store of nearbyStores.stores) { - if ( - store.IsOnlineCapable && - store.IsDeliveryStore && - store.IsOpen && - store.ServiceIsOpen.Delivery && - store.MinDistance < minDistance - ) { - minDistance = store.MinDistance; - nearestStore = store; - } - } - - if (!nearestStore) { - throw new Error("No open stores found for delivery."); - } - - return nearestStore.StoreID; - } catch (error) { - console.error("Error finding nearest store:", error); - throw error; - } - } - - async getOrder(userId: UUID): Promise { - const cachedOrder = await this.runtime.cacheManager.get( - `pizza-order-${userId}` - ); - return cachedOrder || null; - } - - async saveOrder(userId: UUID, order: Order): Promise { - await this.runtime.cacheManager.set(`pizza-order-${userId}`, order); - } - - async getCustomer(userId: UUID): Promise { - const customer = this.runtime.cacheManager.get( - `pizza-customer-${userId}` - ); - return customer || null; - } - - async saveCustomer(userId: UUID, customer: Customer): Promise { - await this.runtime.cacheManager.set( - `pizza-customer-${userId}`, - customer - ); - } - - getNextRequiredAction(order: Order, customer: Customer): string { - if (!order.items || order.items.length === 0) { - return "Collect initial pizza order details"; - } - - if (!customer.name) { - return "Request customer name"; - } - - if (!customer.phone) { - return "Request customer phone number"; - } - - if (!customer.address) { - return "Request delivery address"; - } - - if (!customer.email) { - return "Request email for order confirmation"; - } - - if (!customer.paymentMethod) { - return "Request credit card information for payment"; - } + private readonly headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Referer': 'order.dominos.com' + }; - if (!order.progress.isConfirmed) { - return "Review order details with customer and obtain final confirmation"; - } + private readonly trackerHeaders = { + 'dpz-language': 'en', + 'dpz-market': 'UNITED_STATES', + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=utf-8' + }; - return "Provide order confirmation number and estimated delivery time"; + constructor(private runtime: IAgentRuntime) { + this.BASE_URL = this.runtime.getSetting('API_BASE_URL') || 'https://order.dominos.com/power'; + this.TRACKER_URL = this.runtime.getSetting('API_TRACKER_URL') || 'https://tracker.dominos.com/tracker-presentation-service/v2'; } - getNextRequiredActionDialogue(order: Order, customer: Customer): string { - if (!order.items || order.items.length === 0) { - return "Let me help you build your perfect pizza! What size would you like?"; - } - - if (!customer.name) { - return "Could you please tell me your name for the order?"; - } - - if (!customer.phone) { - return "What phone number can we reach you at if needed?"; - } - - if (!customer.address) { - return "Where would you like your pizza delivered?"; - } - - if (!customer.email) { - return "What email address should we send your order confirmation to?"; - } - - if (!customer.paymentMethod) { - return "To complete your order, I'll need your credit card information. Could you please provide your card number, expiration date (MM/YY), CVV, and billing zip code?"; + // Helper Methods + private getRequiredSetting(name: string): string { + const value = this.runtime.getSetting(name); + if (!value) { + throw new Error(`Required setting ${name} is not configured`); } - - if (!order.progress.isConfirmed) { - return "Great! I have all your information. Would you like me to review everything before placing your order?"; - } - - return "Your order is confirmed! Let me get your confirmation number and estimated delivery time."; + return value; } - // Get topping category and price private getToppingInfo(toppingCode: string): { category: string; price: number; @@ -318,14 +222,11 @@ export class PizzaOrderManager implements OrderManager { throw new Error(`Invalid topping code: ${toppingCode}`); } - // Check for special combinations private checkSpecialCombos(toppings: PizzaTopping[]): number { const toppingCodes = toppings.map((t) => t.code); let maxDiscount = 0; - for (const [_, combo] of Object.entries( - this.menuConfig.specialCombos - )) { + for (const [_, combo] of Object.entries(this.menuConfig.specialCombos)) { if (combo.requiredToppings.every((t) => toppingCodes.includes(t))) { maxDiscount = Math.max(maxDiscount, combo.discount); } @@ -334,12 +235,10 @@ export class PizzaOrderManager implements OrderManager { return maxDiscount; } - // Format currency private formatCurrency(amount: number): string { return `$${amount?.toFixed(2) || "?"}`; } - // Format topping for display with category private formatTopping(topping: PizzaTopping): string { const toppingInfo = this.getToppingInfo(topping.code); const amount = topping.amount > 1 ? "Extra " : ""; @@ -357,94 +256,96 @@ export class PizzaOrderManager implements OrderManager { ); } - // Generate detailed order summary - getOrderSummary(order: Order, customer: Customer): string { - console.log("getOrderSummary: ", order, customer); - let summary = "===== CURRENT ORDER =====\n\n"; - - // Add items - order.items?.forEach((item, index) => { - summary += `PIZZA ${index + 1}\n`; - summary += `==================\n`; - summary += `Size: ${item.size} (${this.formatCurrency(this.menuConfig.basePrices[item.size])})\n`; - summary += `Crust: ${item.crust.replace("_", " ")}`; - - const crustPrice = this.menuConfig.crustPrices[item.crust]; - if (crustPrice > 0) { - summary += ` (+${this.formatCurrency(crustPrice)})\n`; - } else { - summary += "\n"; - } + // Cache Methods + async getOrder(userId: string): Promise { + const cachedOrder = await this.runtime.cacheManager.get( + `pizza-order-${userId}` + ); + return cachedOrder || null; + } - if (item.toppings && item.toppings.length > 0) { - summary += "\nTOPPINGS:\n"; - item.toppings?.forEach((topping) => { - const toppingInfo = this.getToppingInfo(topping.code); - summary += `• ${this.formatTopping(topping)} `; - summary += `(+${this.formatCurrency( - toppingInfo.price * - topping.amount * - (topping.portion === ToppingPortion.ALL ? 1 : 0.5) - )})\n`; - }); - - const comboDiscount = this.checkSpecialCombos(item.toppings); - if (comboDiscount > 0) { - summary += `\nSpecial Combination Discount: -${this.formatCurrency(comboDiscount)}\n`; - } - } else { - summary += "\nClassic Cheese Pizza (No extra toppings)\n"; - } + async saveOrder(userId: string, order: Order): Promise { + await this.runtime.cacheManager.set(`pizza-order-${userId}`, order); + } - if (item.specialInstructions) { - summary += `\nSpecial Instructions:\n${item.specialInstructions}\n`; - } + async getCustomer(userId: string): Promise { + const customer = await this.runtime.cacheManager.get( + `pizza-customer-${userId}` + ); + return customer || null; + } + + async saveCustomer(userId: string, customer: Customer): Promise { + await this.runtime.cacheManager.set( + `pizza-customer-${userId}`, + customer + ); + } + + // API Integration Methods + private async findNearestStore(address: string, city: string, state: string): Promise { + const encodedAddress = encodeURIComponent(address); + const encodedCityState = encodeURIComponent(`${city}, ${state}`); + const url = `${this.BASE_URL}/store-locator?s=${encodedAddress}&c=${encodedCityState}&type=Delivery`; + + const response = await fetch(url, { + method: 'GET', + headers: this.headers + }); + return response.json(); + } - summary += `\nItem Total: ${this.formatCurrency(this.calculatePizzaPrice(item))}\n`; - summary += "==================\n\n"; + private async getStoreInfo(storeId: string): Promise { + const url = `${this.BASE_URL}/store/${storeId}/profile`; + const response = await fetch(url, { + method: 'GET', + headers: this.headers }); + return response.json(); + } - // Add customer info if available - if (customer) { - summary += "CUSTOMER INFORMATION\n"; - summary += "==================\n"; - if (customer.name) summary += `Name: ${customer.name}\n`; - if (customer.phone) summary += `Phone: ${customer.phone}\n`; - if (customer.address) { - summary += "Delivery Address:\n"; - summary += `${(customer?.address && JSON.stringify(customer.address)) || "Not provided"}\n`; - } - if (customer.email) summary += `Email: ${customer.email}\n`; - summary += "==================\n\n"; - } + private async validateOrderWithAPI(orderRequest: OrderRequest): Promise { + const url = `${this.BASE_URL}/validate-order`; + const response = await fetch(url, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ Order: orderRequest }) + }); + return response.json(); + } - // Add payment info if available - if (order.paymentMethod) { - summary += "PAYMENT INFORMATION\n"; - summary += "==================\n"; - summary += `Card: ****${order.paymentMethod.cardNumber.slice(-4)}\n`; - summary += `Status: ${order.paymentStatus}\n`; - summary += "==================\n\n"; - } + private async priceOrderWithAPI(orderRequest: OrderRequest): Promise { + const url = `${this.BASE_URL}/price-order`; + const response = await fetch(url, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ Order: orderRequest }) + }); + return response.json(); + } + + private async placeOrderWithAPI(orderRequest: OrderRequest): Promise { + const url = `${this.BASE_URL}/place-order`; + const response = await fetch(url, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ Order: orderRequest }) + }); + return response.json(); + } - // Add order totals - summary += "ORDER TOTALS\n"; - summary += "==================\n"; - summary += `Subtotal: ${this.formatCurrency(order.total)}\n`; - const tax = order.total * 0.08; // Example tax rate - summary += `Tax (8%): ${this.formatCurrency(tax)}\n`; - const deliveryFee = 3.99; - summary += `Delivery Fee: ${this.formatCurrency(deliveryFee)}\n`; - summary += `Total: ${this.formatCurrency(order.total + tax + deliveryFee)}\n`; - summary += "==================\n"; - - return summary; + private async trackOrderWithAPI(phoneNumber: string): Promise { + const url = `${this.TRACKER_URL}/orders?phonenumber=${phoneNumber.replace(/\D/g, '')}`; + const response = await fetch(url, { + method: 'GET', + headers: this.trackerHeaders + }); + return response.json(); } - // Validate pizza toppings + // Validation Methods private validateToppings(toppings: PizzaTopping[]): OrderError | null { for (const topping of toppings) { - // Check if topping code exists if (!this.menuConfig.availableToppings[topping.code]) { return { type: ErrorType.VALIDATION_FAILED, @@ -453,7 +354,6 @@ export class PizzaOrderManager implements OrderManager { }; } - // Check if portion is valid if (!Object.values(ToppingPortion).includes(topping.portion)) { return { type: ErrorType.VALIDATION_FAILED, @@ -462,7 +362,6 @@ export class PizzaOrderManager implements OrderManager { }; } - // Check if amount is valid (1 for normal, 2 for extra) if (topping.amount !== 1 && topping.amount !== 2) { return { type: ErrorType.VALIDATION_FAILED, @@ -472,7 +371,6 @@ export class PizzaOrderManager implements OrderManager { } } - // Check maximum number of toppings if (toppings.length > 10) { return { type: ErrorType.VALIDATION_FAILED, @@ -484,33 +382,6 @@ export class PizzaOrderManager implements OrderManager { return null; } - // Calculate pizza price including toppings and discounts - private calculatePizzaPrice(item: OrderItem): number { - let price = - this.menuConfig.basePrices[item.size] || - this.menuConfig.basePrices[PizzaSize.MEDIUM]; - - // Add crust price - price += this.menuConfig.crustPrices[item.crust] || 0; - - // Calculate topping prices (continuing calculatePizzaPrice) - if (item.toppings) { - for (const topping of item.toppings) { - const toppingInfo = this.getToppingInfo(topping.code); - const portionMultiplier = - topping.portion === ToppingPortion.ALL ? 1 : 0.5; - price += toppingInfo.price * topping.amount * portionMultiplier; - } - - // Apply combo discounts - const comboDiscount = this.checkSpecialCombos(item.toppings); - price -= comboDiscount; - } - - return price * item.quantity; - } - - // Validate customer information private validateCustomerInfo(customer: Customer): OrderError | null { const phoneRegex = /^\d{3}[-.]?\d{3}[-.]?\d{4}$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; @@ -551,7 +422,6 @@ export class PizzaOrderManager implements OrderManager { return null; } - // Validate payment method private validatePaymentMethod(payment: PaymentMethod): OrderError | null { const cardNumberRegex = /^\d{16}$/; const cvvRegex = /^\d{3,4}$/; @@ -609,110 +479,251 @@ export class PizzaOrderManager implements OrderManager { return null; } - // Calculate order progress - calculateOrderProgress(order: Order, customer: Customer): OrderProgress { + private calculatePizzaPrice(item: OrderItem): number { + let price = this.menuConfig.basePrices[item.size]; + price += this.menuConfig.crustPrices[item.crust] || 0; + + if (item.toppings) { + for (const topping of item.toppings) { + const toppingInfo = this.getToppingInfo(topping.code); + const portionMultiplier = + topping.portion === ToppingPortion.ALL ? 1 : 0.5; + price += toppingInfo.price * topping.amount * portionMultiplier; + } + + const comboDiscount = this.checkSpecialCombos(item.toppings); + price -= comboDiscount; + } + + return price * item.quantity; + } + + private convertItemToProduct(item: OrderItem): DominosProduct { + const sizeMap = { + [PizzaSize.SMALL]: '10', + [PizzaSize.MEDIUM]: '12', + [PizzaSize.LARGE]: '14', + [PizzaSize.XLARGE]: '16' + }; + + const crustMap = { + [PizzaCrust.HAND_TOSSED]: 'HANDTOSS', + [PizzaCrust.THIN]: 'THIN', + [PizzaCrust.PAN]: 'PAN', + [PizzaCrust.GLUTEN_FREE]: 'GLUTENF', + [PizzaCrust.BROOKLYN]: 'BK' + }; + + const code = `${sizeMap[item.size]}${crustMap[item.crust]}`; + const options: { [key: string]: { [key: string]: string } } = { + C: { '1/1': '1' }, // Base cheese + }; + + item.toppings?.forEach((topping) => { + const coverage = topping.portion === ToppingPortion.ALL ? '1/1' : '1/2'; + options[topping.code] = { [coverage]: topping.amount.toString() }; + }); + return { - hasCustomerInfo: Boolean( - customer.name && - customer.phone && - customer.email && - customer.address - ), - hasPaymentMethod: Boolean(customer.paymentMethod), - hasValidPayment: Boolean( - customer.paymentMethod && order.payments?.[0]?.isValid - ), - isConfirmed: order.status === OrderStatus.CONFIRMED, + Code: code, + Options: options }; } - // Process the order - async processOrder( - order: Order, - customer: Customer - ): Promise { - // Validate pizza configuration - if (order && order.items) { - for (const item of order.items) { - // Validate size - if (!Object.values(PizzaSize).includes(item.size)) { - return { - type: ErrorType.VALIDATION_FAILED, - message: `Invalid pizza size: ${item.size}`, - code: "INVALID_SIZE", - }; - } + private convertToOrderRequest(order: Order, customer: Customer): OrderRequest { + const [firstName, ...lastNameParts] = customer.name.split(' '); + const lastName = lastNameParts.join(' '); + + const addressParts = customer.address.split(',').map(part => part.trim()); + const street = addressParts[0]; + const cityStateZip = addressParts[1].split(' '); + const postalCode = cityStateZip.pop() || ''; + const state = cityStateZip.pop() || ''; + const city = cityStateZip.join(' '); + + const orderRequest: OrderRequest = { + Address: { + Street: street, + City: city, + Region: state, + PostalCode: postalCode + }, + StoreID: this.storeId, + Products: order.items?.map(item => this.convertItemToProduct(item)) || [], + OrderChannel: 'OLO', + OrderMethod: 'Web', + LanguageCode: 'en', + ServiceMethod: 'Delivery', + FirstName: firstName, + LastName: lastName, + Email: customer.email, + Phone: customer.phone + }; - // Validate crust - if (!Object.values(PizzaCrust).includes(item.crust)) { - return { - type: ErrorType.VALIDATION_FAILED, - message: `Invalid crust type: ${item.crust}`, - code: "INVALID_CRUST", - }; - } + if (order.paymentMethod && order.paymentMethod.cardNumber) { + orderRequest.Payments = [{ + Type: 'CreditCard', + Amount: order.total, + CardType: this.detectCardType(order.paymentMethod.cardNumber), + Number: order.paymentMethod.cardNumber, + Expiration: order.paymentMethod.expiryDate?.replace('/', '') || '', + SecurityCode: order.paymentMethod.cvv || '', + PostalCode: order.paymentMethod.postalCode || '', + TipAmount: 0 + }]; + } - // Validate toppings - if (item.toppings) { - const toppingError = this.validateToppings(item.toppings); - if (toppingError) return toppingError; - } + return orderRequest; + } - // Validate quantity - if (item.quantity < 1 || item.quantity > 10) { - return { - type: ErrorType.VALIDATION_FAILED, - message: "Quantity must be between 1 and 10", - code: "INVALID_QUANTITY", - }; - } + private detectCardType(cardNumber: string): string { + if (cardNumber.startsWith('4')) return 'VISA'; + if (cardNumber.startsWith('5')) return 'MASTERCARD'; + if (cardNumber.startsWith('34') || cardNumber.startsWith('37')) return 'AMEX'; + if (cardNumber.startsWith('6')) return 'DISCOVER'; + return 'UNKNOWN'; + } + + async getNearestStoreId(address: string): Promise { + try { + const parts = address.split(',').map(part => part.trim()); + const street = parts[0]; + const cityState = parts[1].split(' '); + const state = cityState.pop() || ''; + const city = cityState.join(' '); + + const storeResponse = await this.findNearestStore(street, city, state); + + if (!storeResponse.Stores || storeResponse.Stores.length === 0) { + throw new Error("No nearby stores found."); } - } else { - console.warn("No order items found"); - } - // Calculate total price - if (order.items) { - order.total = order.items?.reduce( - (total, item) => total + this.calculatePizzaPrice(item), - 0 + const deliveryStore = storeResponse.Stores.find((store: any) => + store.IsOnlineCapable && + store.IsDeliveryStore && + store.IsOpen && + store.ServiceIsOpen.Delivery ); - } else { - console.warn("No order items found"); + + if (!deliveryStore) { + throw new Error("No open stores found for delivery."); + } + + this.storeId = deliveryStore.StoreID; + return this.storeId; + + } catch (error) { + console.error("Error finding nearest store:", error); + throw error; } + } - // Validate customer information - const customerError = this.validateCustomerInfo(customer); - if (customerError) return customerError; + async processOrder(order: Order, customer: Customer): Promise { + try { + // Validate customer information + const customerError = this.validateCustomerInfo(customer); + if (customerError) return customerError; + + // Validate order items + if (order.items) { + for (const item of order.items) { + // Validate size + if (!Object.values(PizzaSize).includes(item.size)) { + return { + type: ErrorType.VALIDATION_FAILED, + message: `Invalid pizza size: ${item.size}`, + code: "INVALID_SIZE", + }; + } + + // Validate crust + if (!Object.values(PizzaCrust).includes(item.crust)) { + return { + type: ErrorType.VALIDATION_FAILED, + message: `Invalid crust type: ${item.crust}`, + code: "INVALID_CRUST", + }; + } + + // Validate toppings + if (item.toppings) { + const toppingError = this.validateToppings(item.toppings); + if (toppingError) return toppingError; + } + + // Validate quantity + if (item.quantity < 1 || item.quantity > 10) { + return { + type: ErrorType.VALIDATION_FAILED, + message: "Quantity must be between 1 and 10", + code: "INVALID_QUANTITY", + }; + } + } + } - // Validate payment if provided - if (order.paymentMethod) { - const paymentError = this.validatePaymentMethod( - order.paymentMethod - ); - if (paymentError) { - order.paymentStatus = PaymentStatus.INVALID; - return paymentError; + // Get store ID if not already set + if (!this.storeId) { + this.storeId = await this.getNearestStoreId(customer.address); + } + + // Convert to API format + const orderRequest = this.convertToOrderRequest(order, customer); + + // Validate with API + const validatedOrder = await this.validateOrderWithAPI(orderRequest); + if (validatedOrder.Status !== 'Success') { + return { + type: ErrorType.VALIDATION_FAILED, + message: validatedOrder.StatusItems.join(', '), + code: 'API_VALIDATION_FAILED' + }; + } + + // Price the order + const pricedOrder = await this.priceOrderWithAPI(orderRequest); + if (pricedOrder.Status !== 'Success') { + return { + type: ErrorType.VALIDATION_FAILED, + message: pricedOrder.StatusItems.join(', '), + code: 'API_PRICING_FAILED' + }; } - order.paymentStatus = PaymentStatus.VALID; - } - // Update order progress - order.progress = this.calculateOrderProgress(order, customer); - - // Update order status based on current state - if (order.progress) { - if (!order.progress.hasCustomerInfo) { - order.status = OrderStatus.AWAITING_CUSTOMER_INFO; - } else if (!order.progress.hasValidPayment) { - order.status = OrderStatus.AWAITING_PAYMENT; - } else if (!order.progress.isConfirmed) { - order.status = OrderStatus.PROCESSING; - } else { + // Update total with API price + order.total = pricedOrder.Order.Amounts.Customer; + + // If payment is provided and valid, attempt to place order + if (order.paymentMethod) { + const paymentError = this.validatePaymentMethod(order.paymentMethod); + if (paymentError) { + order.paymentStatus = PaymentStatus.INVALID; + return paymentError; + } + + const placedOrder = await this.placeOrderWithAPI(orderRequest); + if (placedOrder.Status !== 'Success') { + return { + type: ErrorType.PAYMENT_FAILED, + message: placedOrder.StatusItems.join(', '), + code: 'API_ORDER_FAILED' + }; + } + order.status = OrderStatus.CONFIRMED; + order.paymentStatus = PaymentStatus.PROCESSED; + order.progress.isConfirmed = true; } - } - return order; + return order; + + } catch (error) { + console.error('Error processing order:', error); + return { + type: ErrorType.SYSTEM_ERROR, + message: 'An unexpected error occurred while processing your order', + code: 'SYSTEM_ERROR' + }; + } } -} +} \ No newline at end of file diff --git a/packages/plugin-dominos/src/types.ts b/packages/plugin-dominos/src/types.ts index c0a17f3ebc6..f423843115d 100644 --- a/packages/plugin-dominos/src/types.ts +++ b/packages/plugin-dominos/src/types.ts @@ -1,5 +1,5 @@ // Order status enums -enum OrderStatus { +export enum OrderStatus { NEW = "NEW", AWAITING_CUSTOMER_INFO = "AWAITING_CUSTOMER_INFO", AWAITING_PAYMENT = "AWAITING_PAYMENT", @@ -9,24 +9,8 @@ enum OrderStatus { FAILED = "FAILED", } -interface Order { - status: OrderStatus; - paymentStatus: PaymentStatus; - paymentMethod?: PaymentMethod; - customer?: Customer; - items?: OrderItem[]; -} - -// Order progress tracking -interface OrderProgress { - hasCustomerInfo: boolean; - hasPaymentMethod: boolean; - hasValidPayment: boolean; - isConfirmed: boolean; -} - // Payment status types -enum PaymentStatus { +export enum PaymentStatus { NOT_PROVIDED = "NOT_PROVIDED", INVALID = "INVALID", VALID = "VALID", @@ -34,34 +18,8 @@ enum PaymentStatus { PROCESSED = "PROCESSED", } -// Payment method interface -interface PaymentMethod { - type: string; - cardNumber?: string; - expiryDate?: string; - cvv?: string; - postalCode?: string; - isValid: boolean; -} - -// Customer interface -interface Customer { - id?: string; - name: string; - phone: string; - email: string; - address: string; - paymentMethod?: { - cardNumber?: string; - expiryDate?: string; - cvv?: string; - postalCode?: string; - }; - isReturning: boolean; -} - // Pizza size enum -enum PizzaSize { +export enum PizzaSize { SMALL = "SMALL", MEDIUM = "MEDIUM", LARGE = "LARGE", @@ -69,7 +27,7 @@ enum PizzaSize { } // Pizza crust enum -enum PizzaCrust { +export enum PizzaCrust { HAND_TOSSED = "HAND_TOSSED", THIN = "THIN", PAN = "PAN", @@ -78,21 +36,30 @@ enum PizzaCrust { } // Topping portion enum -enum ToppingPortion { +export enum ToppingPortion { LEFT = "LEFT", RIGHT = "RIGHT", ALL = "ALL", } +// Error types +export enum ErrorType { + PAYMENT_FAILED = "PAYMENT_FAILED", + VALIDATION_FAILED = "VALIDATION_FAILED", + CUSTOMER_NOT_FOUND = "CUSTOMER_NOT_FOUND", + SYSTEM_ERROR = "SYSTEM_ERROR", + NETWORK_ERROR = "NETWORK_ERROR", +} + // Pizza topping interface -interface PizzaTopping { +export interface PizzaTopping { code: string; portion: ToppingPortion; amount: number; // 1 for normal, 2 for extra } // Order item interface -interface OrderItem { +export interface OrderItem { productCode: string; size: PizzaSize; crust: PizzaCrust; @@ -101,24 +68,103 @@ interface OrderItem { specialInstructions?: string; } -// Error types -enum ErrorType { - PAYMENT_FAILED = "PAYMENT_FAILED", - VALIDATION_FAILED = "VALIDATION_FAILED", - CUSTOMER_NOT_FOUND = "CUSTOMER_NOT_FOUND", - SYSTEM_ERROR = "SYSTEM_ERROR", - NETWORK_ERROR = "NETWORK_ERROR", +// Payment method interface +export interface PaymentMethod { + type: string; + cardNumber?: string; + expiryDate?: string; + cvv?: string; + postalCode?: string; + isValid: boolean; +} + +// Customer interface +export interface Customer { + id?: string; + name: string; + phone: string; + email: string; + address: string; + paymentMethod?: { + cardNumber?: string; + expiryDate?: string; + cvv?: string; + postalCode?: string; + }; + isReturning: boolean; +} + +// Order progress tracking +export interface OrderProgress { + hasCustomerInfo: boolean; + hasPaymentMethod: boolean; + hasValidPayment: boolean; + isConfirmed: boolean; +} + +// Order interface +export interface Order { + status: OrderStatus; + paymentStatus: PaymentStatus; + paymentMethod?: PaymentMethod; + customer?: Customer; + items?: OrderItem[]; + progress: OrderProgress; + total: number; } // Custom error interface -interface OrderError { +export interface OrderError { type: ErrorType; message: string; code: string; details?: any; } -// Order provider interface +// Dominos API specific types +export interface DominosAddress { + Street: string; + City: string; + Region: string; + PostalCode: string; +} + +export interface DominosPayment { + Type: string; + Amount: number; + CardType: string; + Number: string; + Expiration: string; + SecurityCode: string; + PostalCode: string; + TipAmount: number; +} + +export interface DominosProduct { + Code: string; + Options: { + [key: string]: { + [key: string]: string; + }; + }; +} + +export interface OrderRequest { + Address: DominosAddress; + StoreID: string; + Products: DominosProduct[]; + OrderChannel: string; + OrderMethod: string; + LanguageCode: string; + ServiceMethod: string; + Payments?: DominosPayment[]; + FirstName?: string; + LastName?: string; + Email?: string; + Phone?: string; +} + +// Order manager interface export interface OrderManager { storeId: string; availability: { @@ -140,31 +186,18 @@ export interface OrderManager { requiresPostalCode: boolean; maxFailedAttempts: number; }; + getOrder(userId: string): Promise; + saveOrder(userId: string, order: Order): Promise; + getCustomer(userId: string): Promise; + saveCustomer(userId: string, customer: Customer): Promise; + processOrder(order: Order, customer: Customer): Promise; } // Event types for state management -type OrderEvent = +export type OrderEvent = | { type: "UPDATE_CUSTOMER_INFO"; payload: Partial } | { type: "ADD_ITEM"; payload: OrderItem } | { type: "REMOVE_ITEM"; payload: string } | { type: "UPDATE_PAYMENT"; payload: PaymentMethod } | { type: "PROCESS_ORDER"; payload: Order } - | { type: "HANDLE_ERROR"; payload: OrderError }; - -// Export all types -export { - OrderStatus, - PaymentStatus, - PizzaSize, - PizzaCrust, - ToppingPortion, - ErrorType, - type OrderProgress, - type PaymentMethod, - type Customer, - type PizzaTopping, - type OrderItem, - type Order, - type OrderError, - type OrderEvent, -}; + | { type: "HANDLE_ERROR"; payload: OrderError }; \ No newline at end of file From 4f2a81a52744622ea8bb16087c5f4c85cc7155be Mon Sep 17 00:00:00 2001 From: Sayo Date: Mon, 6 Jan 2025 22:54:52 +0530 Subject: [PATCH 06/11] Fix code scanning alert no. 44: Incomplete string escaping or encoding Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- packages/core/src/parsing.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index 908deec0057..6c42af95b66 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -22,9 +22,9 @@ export const parseShouldRespondFromText = ( const match = text .split("\n")[0] .trim() - .replace("[", "") + .replace(/\[/g, "") .toUpperCase() - .replace("]", "") + .replace(/\]/g, "") .match(/^(RESPOND|IGNORE|STOP)$/i); return match ? (match[0].toUpperCase() as "RESPOND" | "IGNORE" | "STOP") @@ -43,9 +43,9 @@ export const parsePizzaDecisionFromText = ( const match = text .split('\n')[0] .trim() - .replace("[", "") + .replace(/\[/g, "") .toUpperCase() - .replace("]", "") + .replace(/\]/g, "") .match(/^(YES|NO)$/i); return match ? (match[0].toUpperCase() as "YES" | "NO") From 76d9e61a2143391928de0451b6a6187ecf02b538 Mon Sep 17 00:00:00 2001 From: Sayo <82053242+wtfsayo@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:03:26 +0530 Subject: [PATCH 07/11] update/replace `elizaos` --- agent/package.json | 2 +- packages/client-twitter/src/pizza.ts | 154 +++++++----- .../plugin-dominos/src/PizzaOrderManager.ts | 236 ++++++++++-------- 3 files changed, 226 insertions(+), 166 deletions(-) diff --git a/agent/package.json b/agent/package.json index b464cab5a0f..e88a292b03e 100644 --- a/agent/package.json +++ b/agent/package.json @@ -35,7 +35,7 @@ "@elizaos/plugin-aptos": "workspace:*", "@elizaos/plugin-avail": "workspace:*", "@elizaos/plugin-bootstrap": "workspace:*", - "@ai16z/plugin-cosmos": "workspace:*", + "@elizaos/plugin-cosmos": "workspace:*", "@elizaos/plugin-intiface": "workspace:*", "@elizaos/plugin-coinbase": "workspace:*", "@elizaos/plugin-conflux": "workspace:*", diff --git a/packages/client-twitter/src/pizza.ts b/packages/client-twitter/src/pizza.ts index 4d8f49a1a39..58a3e027683 100644 --- a/packages/client-twitter/src/pizza.ts +++ b/packages/client-twitter/src/pizza.ts @@ -1,4 +1,4 @@ -import { IAgentRuntime } from "@ai16z/eliza"; +import { IAgentRuntime } from "@elizaos/core"; // Types and Interfaces interface Address { @@ -57,21 +57,25 @@ export class PizzaAPI { private readonly TRACKER_URL: string; private readonly headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Referer': 'order.dominos.com' + Accept: "application/json", + "Content-Type": "application/json", + Referer: "order.dominos.com", }; private readonly trackerHeaders = { - 'dpz-language': 'en', - 'dpz-market': 'UNITED_STATES', - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8' + "dpz-language": "en", + "dpz-market": "UNITED_STATES", + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", }; constructor(private runtime: IAgentRuntime) { - this.BASE_URL = this.runtime.getSetting('API_BASE_URL') || 'https://order.dominos.com/power'; - this.TRACKER_URL = this.runtime.getSetting('API_TRACKER_URL') || 'https://tracker.dominos.com/tracker-presentation-service/v2'; + this.BASE_URL = + this.runtime.getSetting("API_BASE_URL") || + "https://order.dominos.com/power"; + this.TRACKER_URL = + this.runtime.getSetting("API_TRACKER_URL") || + "https://tracker.dominos.com/tracker-presentation-service/v2"; } // Helper function to get required setting @@ -86,54 +90,61 @@ export class PizzaAPI { // Function to get customer info from settings private getCustomerInfo(): CustomerInfo { return { - FirstName: this.getRequiredSetting('CUSTOMER_FIRST_NAME'), - LastName: this.getRequiredSetting('CUSTOMER_LAST_NAME'), - Email: this.getRequiredSetting('CUSTOMER_EMAIL'), - Phone: this.getRequiredSetting('CUSTOMER_PHONE') + FirstName: this.getRequiredSetting("CUSTOMER_FIRST_NAME"), + LastName: this.getRequiredSetting("CUSTOMER_LAST_NAME"), + Email: this.getRequiredSetting("CUSTOMER_EMAIL"), + Phone: this.getRequiredSetting("CUSTOMER_PHONE"), }; } // Function to get address from settings private getAddress(): Address { return { - Street: this.getRequiredSetting('CUSTOMER_STREET'), - City: this.getRequiredSetting('CUSTOMER_CITY'), - Region: this.getRequiredSetting('CUSTOMER_REGION'), - PostalCode: this.getRequiredSetting('CUSTOMER_POSTAL_CODE') + Street: this.getRequiredSetting("CUSTOMER_STREET"), + City: this.getRequiredSetting("CUSTOMER_CITY"), + Region: this.getRequiredSetting("CUSTOMER_REGION"), + PostalCode: this.getRequiredSetting("CUSTOMER_POSTAL_CODE"), }; } // Function to get payment info from settings private getPayment(amount: number): Payment { return { - Type: 'CreditCard', + Type: "CreditCard", Amount: amount, - CardType: this.detectCardType(this.getRequiredSetting('PAYMENT_CARD_NUMBER')), - Number: this.getRequiredSetting('PAYMENT_CARD_NUMBER'), - Expiration: this.getRequiredSetting('PAYMENT_EXPIRATION'), - SecurityCode: this.getRequiredSetting('PAYMENT_CVV'), - PostalCode: this.getRequiredSetting('PAYMENT_POSTAL_CODE'), - TipAmount: parseFloat(this.getRequiredSetting('PAYMENT_TIP_AMOUNT')) + CardType: this.detectCardType( + this.getRequiredSetting("PAYMENT_CARD_NUMBER") + ), + Number: this.getRequiredSetting("PAYMENT_CARD_NUMBER"), + Expiration: this.getRequiredSetting("PAYMENT_EXPIRATION"), + SecurityCode: this.getRequiredSetting("PAYMENT_CVV"), + PostalCode: this.getRequiredSetting("PAYMENT_POSTAL_CODE"), + TipAmount: parseFloat( + this.getRequiredSetting("PAYMENT_TIP_AMOUNT") + ), }; } private detectCardType(cardNumber: string): string { - if (cardNumber.startsWith('4')) return 'VISA'; - if (cardNumber.startsWith('5')) return 'MASTERCARD'; - if (cardNumber.startsWith('34') || cardNumber.startsWith('37')) return 'AMEX'; - if (cardNumber.startsWith('6')) return 'DISCOVER'; - return 'UNKNOWN'; + if (cardNumber.startsWith("4")) return "VISA"; + if (cardNumber.startsWith("5")) return "MASTERCARD"; + if (cardNumber.startsWith("34") || cardNumber.startsWith("37")) + return "AMEX"; + if (cardNumber.startsWith("6")) return "DISCOVER"; + return "UNKNOWN"; } async findNearestStore(): Promise { const address = this.getAddress(); const encodedAddress = encodeURIComponent(address.Street); - const encodedCityState = encodeURIComponent(`${address.City}, ${address.Region}`); + const encodedCityState = encodeURIComponent( + `${address.City}, ${address.Region}` + ); const url = `${this.BASE_URL}/store-locator?s=${encodedAddress}&c=${encodedCityState}&type=Delivery`; const response = await fetch(url, { - method: 'GET', - headers: this.headers + method: "GET", + headers: this.headers, }); return response.json(); } @@ -141,8 +152,8 @@ export class PizzaAPI { async getStoreInfo(storeId: string): Promise { const url = `${this.BASE_URL}/store/${storeId}/profile`; const response = await fetch(url, { - method: 'GET', - headers: this.headers + method: "GET", + headers: this.headers, }); return response.json(); } @@ -150,9 +161,9 @@ export class PizzaAPI { async validateOrder(orderData: OrderRequest): Promise { const url = `${this.BASE_URL}/validate-order`; const response = await fetch(url, { - method: 'POST', + method: "POST", headers: this.headers, - body: JSON.stringify({ Order: orderData }) + body: JSON.stringify({ Order: orderData }), }); return response.json(); } @@ -160,9 +171,9 @@ export class PizzaAPI { async priceOrder(orderData: OrderRequest): Promise { const url = `${this.BASE_URL}/price-order`; const response = await fetch(url, { - method: 'POST', + method: "POST", headers: this.headers, - body: JSON.stringify({ Order: orderData }) + body: JSON.stringify({ Order: orderData }), }); return response.json(); } @@ -170,19 +181,19 @@ export class PizzaAPI { async placeOrder(orderData: OrderRequest): Promise { const url = `${this.BASE_URL}/place-order`; const response = await fetch(url, { - method: 'POST', + method: "POST", headers: this.headers, - body: JSON.stringify({ Order: orderData }) + body: JSON.stringify({ Order: orderData }), }); return response.json(); } async trackOrder(): Promise { - const customerPhone = this.getRequiredSetting('CUSTOMER_PHONE'); - const url = `${this.TRACKER_URL}/orders?phonenumber=${customerPhone.replace(/\D/g, '')}`; + const customerPhone = this.getRequiredSetting("CUSTOMER_PHONE"); + const url = `${this.TRACKER_URL}/orders?phonenumber=${customerPhone.replace(/\D/g, "")}`; const response = await fetch(url, { - method: 'GET', - headers: this.trackerHeaders + method: "GET", + headers: this.trackerHeaders, }); return response.json(); } @@ -191,38 +202,46 @@ export class PizzaAPI { try { // 1. Find nearest store using settings address const storeResponse = await this.findNearestStore(); - console.log('Store Response:', JSON.stringify(storeResponse, null, 2)); + console.log( + "Store Response:", + JSON.stringify(storeResponse, null, 2) + ); const storeId = storeResponse.Stores[0].StoreID; // 2. Get store info const storeInfo = await this.getStoreInfo(storeId); - console.log('Store Info:', JSON.stringify(storeInfo, null, 2)); + console.log("Store Info:", JSON.stringify(storeInfo, null, 2)); // 3. Create order request const address = this.getAddress(); const orderRequest: OrderRequest = { Address: address, StoreID: storeId, - Products: [{ - Code: '14SCREEN', - Options: { - X: { '1/1': '1' }, - C: { '1/1': '1' }, - } - }], - OrderChannel: 'OLO', - OrderMethod: 'Web', - LanguageCode: 'en', - ServiceMethod: 'Delivery' + Products: [ + { + Code: "14SCREEN", + Options: { + X: { "1/1": "1" }, + C: { "1/1": "1" }, + }, + }, + ], + OrderChannel: "OLO", + OrderMethod: "Web", + LanguageCode: "en", + ServiceMethod: "Delivery", }; // 4. Validate order const validatedOrder = await this.validateOrder(orderRequest); - console.log('Validated Order:', JSON.stringify(validatedOrder, null, 2)); + console.log( + "Validated Order:", + JSON.stringify(validatedOrder, null, 2) + ); // 5. Price order const pricedOrder = await this.priceOrder(orderRequest); - console.log('Priced Order:', JSON.stringify(pricedOrder, null, 2)); + console.log("Priced Order:", JSON.stringify(pricedOrder, null, 2)); // 6. Add payment and customer info for final order const customerInfo = this.getCustomerInfo(); @@ -232,25 +251,28 @@ export class PizzaAPI { LastName: customerInfo.LastName, Email: customerInfo.Email, Phone: customerInfo.Phone, - Payments: [this.getPayment(pricedOrder.Order.Amounts.Customer)] + Payments: [this.getPayment(pricedOrder.Order.Amounts.Customer)], }; // 7. Place order const placedOrder = await this.placeOrder(finalOrder); - console.log('Placed Order:', JSON.stringify(placedOrder, null, 2)); + console.log("Placed Order:", JSON.stringify(placedOrder, null, 2)); // 8. Track order const trackingInfo = await this.trackOrder(); - console.log('Tracking Info:', JSON.stringify(trackingInfo, null, 2)); + console.log( + "Tracking Info:", + JSON.stringify(trackingInfo, null, 2) + ); return { storeInfo, orderDetails: placedOrder, - tracking: trackingInfo + tracking: trackingInfo, }; } catch (error) { - console.error('Error ordering pizza:', error); + console.error("Error ordering pizza:", error); throw error; } } -} \ No newline at end of file +} diff --git a/packages/plugin-dominos/src/PizzaOrderManager.ts b/packages/plugin-dominos/src/PizzaOrderManager.ts index 56ff98c3bb0..3f8fdcc26ef 100644 --- a/packages/plugin-dominos/src/PizzaOrderManager.ts +++ b/packages/plugin-dominos/src/PizzaOrderManager.ts @@ -1,4 +1,4 @@ -import { IAgentRuntime } from "@ai16z/eliza"; +import { IAgentRuntime } from "@elizaos/core"; import { Customer, ErrorType, @@ -17,7 +17,7 @@ import { ToppingPortion, DominosPayment, DominosAddress, - DominosProduct + DominosProduct, } from "./types"; export class PizzaOrderManager implements OrderManager { @@ -171,21 +171,25 @@ export class PizzaOrderManager implements OrderManager { private readonly TRACKER_URL: string; private readonly headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Referer': 'order.dominos.com' + Accept: "application/json", + "Content-Type": "application/json", + Referer: "order.dominos.com", }; private readonly trackerHeaders = { - 'dpz-language': 'en', - 'dpz-market': 'UNITED_STATES', - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=utf-8' + "dpz-language": "en", + "dpz-market": "UNITED_STATES", + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", }; constructor(private runtime: IAgentRuntime) { - this.BASE_URL = this.runtime.getSetting('API_BASE_URL') || 'https://order.dominos.com/power'; - this.TRACKER_URL = this.runtime.getSetting('API_TRACKER_URL') || 'https://tracker.dominos.com/tracker-presentation-service/v2'; + this.BASE_URL = + this.runtime.getSetting("API_BASE_URL") || + "https://order.dominos.com/power"; + this.TRACKER_URL = + this.runtime.getSetting("API_TRACKER_URL") || + "https://tracker.dominos.com/tracker-presentation-service/v2"; } // Helper Methods @@ -226,7 +230,9 @@ export class PizzaOrderManager implements OrderManager { const toppingCodes = toppings.map((t) => t.code); let maxDiscount = 0; - for (const [_, combo] of Object.entries(this.menuConfig.specialCombos)) { + for (const [_, combo] of Object.entries( + this.menuConfig.specialCombos + )) { if (combo.requiredToppings.every((t) => toppingCodes.includes(t))) { maxDiscount = Math.max(maxDiscount, combo.discount); } @@ -283,14 +289,18 @@ export class PizzaOrderManager implements OrderManager { } // API Integration Methods - private async findNearestStore(address: string, city: string, state: string): Promise { + private async findNearestStore( + address: string, + city: string, + state: string + ): Promise { const encodedAddress = encodeURIComponent(address); const encodedCityState = encodeURIComponent(`${city}, ${state}`); const url = `${this.BASE_URL}/store-locator?s=${encodedAddress}&c=${encodedCityState}&type=Delivery`; const response = await fetch(url, { - method: 'GET', - headers: this.headers + method: "GET", + headers: this.headers, }); return response.json(); } @@ -298,18 +308,20 @@ export class PizzaOrderManager implements OrderManager { private async getStoreInfo(storeId: string): Promise { const url = `${this.BASE_URL}/store/${storeId}/profile`; const response = await fetch(url, { - method: 'GET', - headers: this.headers + method: "GET", + headers: this.headers, }); return response.json(); } - private async validateOrderWithAPI(orderRequest: OrderRequest): Promise { + private async validateOrderWithAPI( + orderRequest: OrderRequest + ): Promise { const url = `${this.BASE_URL}/validate-order`; const response = await fetch(url, { - method: 'POST', + method: "POST", headers: this.headers, - body: JSON.stringify({ Order: orderRequest }) + body: JSON.stringify({ Order: orderRequest }), }); return response.json(); } @@ -317,9 +329,9 @@ export class PizzaOrderManager implements OrderManager { private async priceOrderWithAPI(orderRequest: OrderRequest): Promise { const url = `${this.BASE_URL}/price-order`; const response = await fetch(url, { - method: 'POST', + method: "POST", headers: this.headers, - body: JSON.stringify({ Order: orderRequest }) + body: JSON.stringify({ Order: orderRequest }), }); return response.json(); } @@ -327,18 +339,18 @@ export class PizzaOrderManager implements OrderManager { private async placeOrderWithAPI(orderRequest: OrderRequest): Promise { const url = `${this.BASE_URL}/place-order`; const response = await fetch(url, { - method: 'POST', + method: "POST", headers: this.headers, - body: JSON.stringify({ Order: orderRequest }) + body: JSON.stringify({ Order: orderRequest }), }); return response.json(); } private async trackOrderWithAPI(phoneNumber: string): Promise { - const url = `${this.TRACKER_URL}/orders?phonenumber=${phoneNumber.replace(/\D/g, '')}`; + const url = `${this.TRACKER_URL}/orders?phonenumber=${phoneNumber.replace(/\D/g, "")}`; const response = await fetch(url, { - method: 'GET', - headers: this.trackerHeaders + method: "GET", + headers: this.trackerHeaders, }); return response.json(); } @@ -500,109 +512,128 @@ export class PizzaOrderManager implements OrderManager { private convertItemToProduct(item: OrderItem): DominosProduct { const sizeMap = { - [PizzaSize.SMALL]: '10', - [PizzaSize.MEDIUM]: '12', - [PizzaSize.LARGE]: '14', - [PizzaSize.XLARGE]: '16' + [PizzaSize.SMALL]: "10", + [PizzaSize.MEDIUM]: "12", + [PizzaSize.LARGE]: "14", + [PizzaSize.XLARGE]: "16", }; const crustMap = { - [PizzaCrust.HAND_TOSSED]: 'HANDTOSS', - [PizzaCrust.THIN]: 'THIN', - [PizzaCrust.PAN]: 'PAN', - [PizzaCrust.GLUTEN_FREE]: 'GLUTENF', - [PizzaCrust.BROOKLYN]: 'BK' + [PizzaCrust.HAND_TOSSED]: "HANDTOSS", + [PizzaCrust.THIN]: "THIN", + [PizzaCrust.PAN]: "PAN", + [PizzaCrust.GLUTEN_FREE]: "GLUTENF", + [PizzaCrust.BROOKLYN]: "BK", }; const code = `${sizeMap[item.size]}${crustMap[item.crust]}`; const options: { [key: string]: { [key: string]: string } } = { - C: { '1/1': '1' }, // Base cheese + C: { "1/1": "1" }, // Base cheese }; item.toppings?.forEach((topping) => { - const coverage = topping.portion === ToppingPortion.ALL ? '1/1' : '1/2'; + const coverage = + topping.portion === ToppingPortion.ALL ? "1/1" : "1/2"; options[topping.code] = { [coverage]: topping.amount.toString() }; }); return { Code: code, - Options: options + Options: options, }; } - private convertToOrderRequest(order: Order, customer: Customer): OrderRequest { - const [firstName, ...lastNameParts] = customer.name.split(' '); - const lastName = lastNameParts.join(' '); + private convertToOrderRequest( + order: Order, + customer: Customer + ): OrderRequest { + const [firstName, ...lastNameParts] = customer.name.split(" "); + const lastName = lastNameParts.join(" "); - const addressParts = customer.address.split(',').map(part => part.trim()); + const addressParts = customer.address + .split(",") + .map((part) => part.trim()); const street = addressParts[0]; - const cityStateZip = addressParts[1].split(' '); - const postalCode = cityStateZip.pop() || ''; - const state = cityStateZip.pop() || ''; - const city = cityStateZip.join(' '); + const cityStateZip = addressParts[1].split(" "); + const postalCode = cityStateZip.pop() || ""; + const state = cityStateZip.pop() || ""; + const city = cityStateZip.join(" "); const orderRequest: OrderRequest = { Address: { Street: street, City: city, Region: state, - PostalCode: postalCode + PostalCode: postalCode, }, StoreID: this.storeId, - Products: order.items?.map(item => this.convertItemToProduct(item)) || [], - OrderChannel: 'OLO', - OrderMethod: 'Web', - LanguageCode: 'en', - ServiceMethod: 'Delivery', + Products: + order.items?.map((item) => this.convertItemToProduct(item)) || + [], + OrderChannel: "OLO", + OrderMethod: "Web", + LanguageCode: "en", + ServiceMethod: "Delivery", FirstName: firstName, LastName: lastName, Email: customer.email, - Phone: customer.phone + Phone: customer.phone, }; if (order.paymentMethod && order.paymentMethod.cardNumber) { - orderRequest.Payments = [{ - Type: 'CreditCard', - Amount: order.total, - CardType: this.detectCardType(order.paymentMethod.cardNumber), - Number: order.paymentMethod.cardNumber, - Expiration: order.paymentMethod.expiryDate?.replace('/', '') || '', - SecurityCode: order.paymentMethod.cvv || '', - PostalCode: order.paymentMethod.postalCode || '', - TipAmount: 0 - }]; + orderRequest.Payments = [ + { + Type: "CreditCard", + Amount: order.total, + CardType: this.detectCardType( + order.paymentMethod.cardNumber + ), + Number: order.paymentMethod.cardNumber, + Expiration: + order.paymentMethod.expiryDate?.replace("/", "") || "", + SecurityCode: order.paymentMethod.cvv || "", + PostalCode: order.paymentMethod.postalCode || "", + TipAmount: 0, + }, + ]; } return orderRequest; } private detectCardType(cardNumber: string): string { - if (cardNumber.startsWith('4')) return 'VISA'; - if (cardNumber.startsWith('5')) return 'MASTERCARD'; - if (cardNumber.startsWith('34') || cardNumber.startsWith('37')) return 'AMEX'; - if (cardNumber.startsWith('6')) return 'DISCOVER'; - return 'UNKNOWN'; + if (cardNumber.startsWith("4")) return "VISA"; + if (cardNumber.startsWith("5")) return "MASTERCARD"; + if (cardNumber.startsWith("34") || cardNumber.startsWith("37")) + return "AMEX"; + if (cardNumber.startsWith("6")) return "DISCOVER"; + return "UNKNOWN"; } async getNearestStoreId(address: string): Promise { try { - const parts = address.split(',').map(part => part.trim()); + const parts = address.split(",").map((part) => part.trim()); const street = parts[0]; - const cityState = parts[1].split(' '); - const state = cityState.pop() || ''; - const city = cityState.join(' '); - - const storeResponse = await this.findNearestStore(street, city, state); + const cityState = parts[1].split(" "); + const state = cityState.pop() || ""; + const city = cityState.join(" "); + + const storeResponse = await this.findNearestStore( + street, + city, + state + ); if (!storeResponse.Stores || storeResponse.Stores.length === 0) { throw new Error("No nearby stores found."); } - const deliveryStore = storeResponse.Stores.find((store: any) => - store.IsOnlineCapable && - store.IsDeliveryStore && - store.IsOpen && - store.ServiceIsOpen.Delivery + const deliveryStore = storeResponse.Stores.find( + (store: any) => + store.IsOnlineCapable && + store.IsDeliveryStore && + store.IsOpen && + store.ServiceIsOpen.Delivery ); if (!deliveryStore) { @@ -611,14 +642,16 @@ export class PizzaOrderManager implements OrderManager { this.storeId = deliveryStore.StoreID; return this.storeId; - } catch (error) { console.error("Error finding nearest store:", error); throw error; } } - async processOrder(order: Order, customer: Customer): Promise { + async processOrder( + order: Order, + customer: Customer + ): Promise { try { // Validate customer information const customerError = this.validateCustomerInfo(customer); @@ -647,7 +680,9 @@ export class PizzaOrderManager implements OrderManager { // Validate toppings if (item.toppings) { - const toppingError = this.validateToppings(item.toppings); + const toppingError = this.validateToppings( + item.toppings + ); if (toppingError) return toppingError; } @@ -671,22 +706,23 @@ export class PizzaOrderManager implements OrderManager { const orderRequest = this.convertToOrderRequest(order, customer); // Validate with API - const validatedOrder = await this.validateOrderWithAPI(orderRequest); - if (validatedOrder.Status !== 'Success') { + const validatedOrder = + await this.validateOrderWithAPI(orderRequest); + if (validatedOrder.Status !== "Success") { return { type: ErrorType.VALIDATION_FAILED, - message: validatedOrder.StatusItems.join(', '), - code: 'API_VALIDATION_FAILED' + message: validatedOrder.StatusItems.join(", "), + code: "API_VALIDATION_FAILED", }; } // Price the order const pricedOrder = await this.priceOrderWithAPI(orderRequest); - if (pricedOrder.Status !== 'Success') { + if (pricedOrder.Status !== "Success") { return { type: ErrorType.VALIDATION_FAILED, - message: pricedOrder.StatusItems.join(', '), - code: 'API_PRICING_FAILED' + message: pricedOrder.StatusItems.join(", "), + code: "API_PRICING_FAILED", }; } @@ -695,18 +731,20 @@ export class PizzaOrderManager implements OrderManager { // If payment is provided and valid, attempt to place order if (order.paymentMethod) { - const paymentError = this.validatePaymentMethod(order.paymentMethod); + const paymentError = this.validatePaymentMethod( + order.paymentMethod + ); if (paymentError) { order.paymentStatus = PaymentStatus.INVALID; return paymentError; } const placedOrder = await this.placeOrderWithAPI(orderRequest); - if (placedOrder.Status !== 'Success') { + if (placedOrder.Status !== "Success") { return { type: ErrorType.PAYMENT_FAILED, - message: placedOrder.StatusItems.join(', '), - code: 'API_ORDER_FAILED' + message: placedOrder.StatusItems.join(", "), + code: "API_ORDER_FAILED", }; } @@ -716,14 +754,14 @@ export class PizzaOrderManager implements OrderManager { } return order; - } catch (error) { - console.error('Error processing order:', error); + console.error("Error processing order:", error); return { type: ErrorType.SYSTEM_ERROR, - message: 'An unexpected error occurred while processing your order', - code: 'SYSTEM_ERROR' + message: + "An unexpected error occurred while processing your order", + code: "SYSTEM_ERROR", }; } } -} \ No newline at end of file +} From f68c66f92d79db3d504bd09fd2cbc7bb11086098 Mon Sep 17 00:00:00 2001 From: Sayo <82053242+wtfsayo@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:10:12 +0530 Subject: [PATCH 08/11] test pr --- agent/package.json | 2 +- packages/plugin-dominos/package.json | 2 +- packages/plugin-dominos/src/actions/confirmOrder.ts | 2 +- packages/plugin-dominos/src/actions/endOrder.ts | 2 +- packages/plugin-dominos/src/actions/startOrder.ts | 2 +- packages/plugin-dominos/src/actions/updateCustomer.ts | 2 +- packages/plugin-dominos/src/actions/updateOrder.ts | 2 +- packages/plugin-dominos/src/index.ts | 2 +- packages/plugin-dominos/src/providers/pizzaOrder.ts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/agent/package.json b/agent/package.json index e88a292b03e..b464cab5a0f 100644 --- a/agent/package.json +++ b/agent/package.json @@ -35,7 +35,7 @@ "@elizaos/plugin-aptos": "workspace:*", "@elizaos/plugin-avail": "workspace:*", "@elizaos/plugin-bootstrap": "workspace:*", - "@elizaos/plugin-cosmos": "workspace:*", + "@ai16z/plugin-cosmos": "workspace:*", "@elizaos/plugin-intiface": "workspace:*", "@elizaos/plugin-coinbase": "workspace:*", "@elizaos/plugin-conflux": "workspace:*", diff --git a/packages/plugin-dominos/package.json b/packages/plugin-dominos/package.json index aeda54a110c..c457fe54487 100644 --- a/packages/plugin-dominos/package.json +++ b/packages/plugin-dominos/package.json @@ -5,7 +5,7 @@ "type": "module", "types": "dist/index.d.ts", "dependencies": { - "@ai16z/eliza": "workspace:*", + "@elizaos/core": "workspace:*", "dominos": "^3.3.1", "tsup": "8.3.5" }, diff --git a/packages/plugin-dominos/src/actions/confirmOrder.ts b/packages/plugin-dominos/src/actions/confirmOrder.ts index 6c2e8ccea1f..5578307b9d7 100644 --- a/packages/plugin-dominos/src/actions/confirmOrder.ts +++ b/packages/plugin-dominos/src/actions/confirmOrder.ts @@ -1,4 +1,4 @@ -import { Action, IAgentRuntime, Memory } from "@ai16z/eliza"; +import { Action, IAgentRuntime, Memory } from "@elizaos/core"; import { PizzaOrderManager } from "../PizzaOrderManager"; import { OrderStatus } from "../types"; diff --git a/packages/plugin-dominos/src/actions/endOrder.ts b/packages/plugin-dominos/src/actions/endOrder.ts index 12c5ae50d7a..53818150267 100644 --- a/packages/plugin-dominos/src/actions/endOrder.ts +++ b/packages/plugin-dominos/src/actions/endOrder.ts @@ -1,4 +1,4 @@ -import { Action, ActionExample, IAgentRuntime, Memory } from "@ai16z/eliza"; +import { Action, ActionExample, IAgentRuntime, Memory } from "@elizaos/core"; import { PizzaOrderManager } from "../PizzaOrderManager"; export const endOrder: Action = { diff --git a/packages/plugin-dominos/src/actions/startOrder.ts b/packages/plugin-dominos/src/actions/startOrder.ts index 0a6cb48b268..3b12adc9fd0 100644 --- a/packages/plugin-dominos/src/actions/startOrder.ts +++ b/packages/plugin-dominos/src/actions/startOrder.ts @@ -8,7 +8,7 @@ import { Memory, ModelClass, State, -} from "@ai16z/eliza"; +} from "@elizaos/core"; import { Customer, Item, Order } from "dominos"; import { PizzaCrust, PizzaSize } from "../types"; diff --git a/packages/plugin-dominos/src/actions/updateCustomer.ts b/packages/plugin-dominos/src/actions/updateCustomer.ts index 289b5a77d2a..3e66a09e83a 100644 --- a/packages/plugin-dominos/src/actions/updateCustomer.ts +++ b/packages/plugin-dominos/src/actions/updateCustomer.ts @@ -8,7 +8,7 @@ import { Memory, ModelClass, State, -} from "@ai16z/eliza"; +} from "@elizaos/core"; import { Customer, Payment } from "dominos"; import { z } from "zod"; import { PizzaOrderManager } from "../PizzaOrderManager"; diff --git a/packages/plugin-dominos/src/actions/updateOrder.ts b/packages/plugin-dominos/src/actions/updateOrder.ts index 547247c3b09..6c75d22ad43 100644 --- a/packages/plugin-dominos/src/actions/updateOrder.ts +++ b/packages/plugin-dominos/src/actions/updateOrder.ts @@ -8,7 +8,7 @@ import { Memory, ModelClass, State, -} from "@ai16z/eliza"; +} from "@elizaos/core"; import { Item } from "dominos"; import { PizzaCrust, PizzaSize, ToppingPortion } from "../types"; diff --git a/packages/plugin-dominos/src/index.ts b/packages/plugin-dominos/src/index.ts index 97b07c43cb7..e5dbf7c3154 100644 --- a/packages/plugin-dominos/src/index.ts +++ b/packages/plugin-dominos/src/index.ts @@ -1,4 +1,4 @@ -import { Plugin } from "@ai16z/eliza"; +import { Plugin } from "@elizaos/core"; import { startOrder } from "./actions/startOrder.ts"; import { pizzaOrderProvider } from "./providers/pizzaOrder.ts"; import { endOrder } from "./actions/endOrder.ts"; diff --git a/packages/plugin-dominos/src/providers/pizzaOrder.ts b/packages/plugin-dominos/src/providers/pizzaOrder.ts index d9cb579f359..7a91cfc4528 100644 --- a/packages/plugin-dominos/src/providers/pizzaOrder.ts +++ b/packages/plugin-dominos/src/providers/pizzaOrder.ts @@ -1,4 +1,4 @@ -import { IAgentRuntime, Memory, Provider } from "@ai16z/eliza"; +import { IAgentRuntime, Memory, Provider } from "@elizaos/core"; import { PizzaOrderManager } from "../PizzaOrderManager"; import { OrderStatus, PaymentStatus } from "../types"; From 22563357278d58b457daf222003265e0b60459c4 Mon Sep 17 00:00:00 2001 From: Sayo <82053242+wtfsayo@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:29:09 +0530 Subject: [PATCH 09/11] enhance existing actions and Order Manager --- packages/plugin-dominos/package.json | 2 +- .../plugin-dominos/src/PizzaOrderManager.ts | 18 +++ .../src/actions/confirmOrder.ts | 114 ++++++++++++------ .../plugin-dominos/src/actions/startOrder.ts | 2 +- .../src/actions/updateCustomer.ts | 4 +- .../plugin-dominos/src/actions/updateOrder.ts | 4 +- pnpm-lock.yaml | 105 ++++++++++++++++ 7 files changed, 203 insertions(+), 46 deletions(-) diff --git a/packages/plugin-dominos/package.json b/packages/plugin-dominos/package.json index c457fe54487..572d6039383 100644 --- a/packages/plugin-dominos/package.json +++ b/packages/plugin-dominos/package.json @@ -1,5 +1,5 @@ { - "name": "@ai16z/plugin-dominos", + "name": "@elizaos/plugin-dominos", "version": "0.1.5-alpha.5", "main": "dist/index.js", "type": "module", diff --git a/packages/plugin-dominos/src/PizzaOrderManager.ts b/packages/plugin-dominos/src/PizzaOrderManager.ts index 3f8fdcc26ef..62156909c17 100644 --- a/packages/plugin-dominos/src/PizzaOrderManager.ts +++ b/packages/plugin-dominos/src/PizzaOrderManager.ts @@ -764,4 +764,22 @@ export class PizzaOrderManager implements OrderManager { }; } } + + getOrderSummary(order: Order, customer: Customer): string { + // Format order details into readable summary + return `Order Summary:\n${order.items.map(item => + `- ${item.quantity}x ${item.size} ${item.crust} Pizza${ + item.toppings?.length ? ` with ${item.toppings.map(t => + `${t.amount}x ${t.code} (${t.portion})`).join(', ')}` : '' + }` + ).join('\n')}`; + } + + getNextRequiredActionDialogue(order: Order, customer: Customer): string { + // Return appropriate next step prompt + if (!order.items[0].size) return "What size pizza would you like?"; + if (!order.items[0].crust) return "What type of crust would you prefer?"; + if (!order.items[0].toppings?.length) return "What toppings would you like?"; + return "Would you like to add any more items to your order?"; + } } diff --git a/packages/plugin-dominos/src/actions/confirmOrder.ts b/packages/plugin-dominos/src/actions/confirmOrder.ts index 5578307b9d7..204da2c1229 100644 --- a/packages/plugin-dominos/src/actions/confirmOrder.ts +++ b/packages/plugin-dominos/src/actions/confirmOrder.ts @@ -1,58 +1,92 @@ -import { Action, IAgentRuntime, Memory } from "@elizaos/core"; +import { Action, ActionExample, IAgentRuntime, Memory } from "@elizaos/core"; import { PizzaOrderManager } from "../PizzaOrderManager"; -import { OrderStatus } from "../types"; +import { OrderStatus, PaymentStatus } from "../types"; export const confirmOrder: Action = { name: "CONFIRM_ORDER", - similes: ["FINALIZE_ORDER", "FINISH_ORDER", "PLACE_ORDER"], + description: "Confirms and places the current pizza order.", + similes: ["PLACE_ORDER", "SUBMIT_ORDER", "FINALIZE_ORDER"], examples: [ - // TODO - ], - description: "Confirms and places the final order with Dominos", - validate: async (runtime: IAgentRuntime, message: Memory) => { + [ + { + user: "{{user1}}", + content: { + text: "Yes, please place my order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Great! I'll place your order now. Your pizza will be ready soon!", + action: "CONFIRM_ORDER", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Everything looks good, confirm the order", + }, + }, + { + user: "{{user2}}", + content: { + text: "Perfect! I'm confirming your order now. You'll receive a confirmation shortly!", + action: "CONFIRM_ORDER", + }, + }, + ], + ] as ActionExample[][], + handler: async (runtime: IAgentRuntime, message: Memory) => { const orderManager = new PizzaOrderManager(runtime); const userId = message.userId; + + // Get the active order and customer const order = await orderManager.getOrder(userId); + if (!order) { + return "There is no active order to confirm. Please start a new order first."; + } + const customer = await orderManager.getCustomer(userId); + if (!customer) { + return "Customer details not found. Please provide your information before confirming the order."; + } - if (!order || !customer) return false; + // Validate order status + if (order.status !== OrderStatus.PROCESSING) { + return "The order is not ready to be confirmed. Please complete all required information first."; + } - // Only valid if we have complete customer info and valid payment - return ( - order.progress && - order.progress.hasCustomerInfo && - order.progress.hasValidPayment && - !order.progress.isConfirmed - ); - }, - handler: async (runtime: IAgentRuntime, message: Memory) => { - const orderManager = new PizzaOrderManager(runtime); - const userId = message.userId; - const order = await orderManager.getOrder(userId); - const customer = await orderManager.getCustomer(userId); + // Check payment status + if (order.paymentStatus !== PaymentStatus.VALID) { + return "Please provide valid payment information before confirming the order."; + } - try { - // Final validation with Dominos - await order.validate(); + // Process and place the order + const processedOrder = await orderManager.processOrder(order, customer); + if ("type" in processedOrder) { + return `Unable to place order: ${processedOrder.message}`; + } - // Get final pricing - await order.price(); + // Update order status + processedOrder.status = OrderStatus.CONFIRMED; + await orderManager.saveOrder(userId, processedOrder); - // Place the order - await order.place(); + return "Your order has been confirmed and is being prepared! You'll receive updates on your order status."; + }, + validate: async (runtime: IAgentRuntime, message: Memory) => { + const orderManager = new PizzaOrderManager(runtime); + const userId = message.userId; - // Update order status - order.status = OrderStatus.CONFIRMED; - await orderManager.saveOrder(userId, order); + // Get the active order + const order = await orderManager.getOrder(userId); + if (!order) return false; - return ( - `Great news! Your order has been confirmed and is being prepared.\n\n` + - `Order Number: ${order.orderID}\n` + - `Estimated Delivery Time: ${order.estimatedWaitMinutes} minutes\n\n` + - orderManager.getOrderSummary(order, customer) - ); - } catch (error) { - return "There was an issue placing your order: " + error.message; - } + // Check if order is in a state that can be confirmed + return ( + order.status === OrderStatus.PROCESSING && + order.paymentStatus === PaymentStatus.VALID + ); }, }; diff --git a/packages/plugin-dominos/src/actions/startOrder.ts b/packages/plugin-dominos/src/actions/startOrder.ts index 3b12adc9fd0..2f80119e118 100644 --- a/packages/plugin-dominos/src/actions/startOrder.ts +++ b/packages/plugin-dominos/src/actions/startOrder.ts @@ -2,7 +2,7 @@ import { Action, ActionExample, composeContext, - generateObjectV2, + generateObject, Handler, IAgentRuntime, Memory, diff --git a/packages/plugin-dominos/src/actions/updateCustomer.ts b/packages/plugin-dominos/src/actions/updateCustomer.ts index 3e66a09e83a..8e9f2d6dbf0 100644 --- a/packages/plugin-dominos/src/actions/updateCustomer.ts +++ b/packages/plugin-dominos/src/actions/updateCustomer.ts @@ -2,7 +2,7 @@ import { Action, ActionExample, composeContext, - generateObjectV2, + generateObject, Handler, IAgentRuntime, Memory, @@ -85,7 +85,7 @@ Provide updated customer information as a JSON object, including only fields tha }); try { - const customerUpdates = (await generateObjectV2({ + const customerUpdates = (await generateObject({ runtime, context, modelClass: ModelClass.LARGE, diff --git a/packages/plugin-dominos/src/actions/updateOrder.ts b/packages/plugin-dominos/src/actions/updateOrder.ts index 6c75d22ad43..41be571b112 100644 --- a/packages/plugin-dominos/src/actions/updateOrder.ts +++ b/packages/plugin-dominos/src/actions/updateOrder.ts @@ -2,7 +2,7 @@ import { Action, ActionExample, composeContext, - generateObjectV2, + generateObject, Handler, IAgentRuntime, Memory, @@ -143,7 +143,7 @@ export const handler: Handler = async ( }); try { - const orderUpdates = (await generateObjectV2({ + const orderUpdates = (await generateObject({ runtime, context, modelClass: ModelClass.LARGE, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c304f2f7396..897775bdfdc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1203,6 +1203,21 @@ importers: specifier: 7.1.0 version: 7.1.0 + packages/plugin-dominos: + dependencies: + '@elizaos/core': + specifier: workspace:* + version: link:../core + dominos: + specifier: ^3.3.1 + version: 3.3.1(encoding@0.1.13) + tsup: + specifier: 8.3.5 + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.7.0) + whatwg-url: + specifier: 7.1.0 + version: 7.1.0 + packages/plugin-echochambers: dependencies: '@elizaos/core': @@ -8961,6 +8976,10 @@ packages: ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-colors-es6@5.0.0: + resolution: {integrity: sha512-//DAVWjZto+Mmbm8czZxrwC1/QMi5Ka+c8H6jViO1L3McHYE5YLypSFP44EyrJVzPnTnnxOsjOHjLB262eNoDA==} + engines: {node: '>=13'} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -9438,6 +9457,10 @@ packages: bip39@3.1.0: resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==} + biskviit@1.0.1: + resolution: {integrity: sha512-VGCXdHbdbpEkFgtjkeoBN8vRlbj1ZRX2/mxhE8asCCRalUx2nBzOomLJv8Aw/nRt5+ccDb+tPKidg4XxcfGW4w==} + engines: {node: '>=1.0.0'} + bitcoinjs-lib@7.0.0-rc.0: resolution: {integrity: sha512-7CQgOIbREemKR/NT2uc3uO/fkEy+6CM0sLxboVVY6bv6DbZmPt3gg5Y/hhWgQFeZu5lfTbtVAv32MIxf7lMh4g==} engines: {node: '>=18.0.0'} @@ -11154,6 +11177,10 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dominos@3.3.1: + resolution: {integrity: sha512-CAXN0K71Jlsl4iOAzu95p/lFFVxeQSbtn0yz/24SIpU8EMcRa8ZVjxjJjPBsxnauyM0itbxBPTqiZoj4oBn6WA==} + engines: {node: '>=14.0.0'} + dompurify@3.2.2: resolution: {integrity: sha512-YMM+erhdZ2nkZ4fTNRTSI94mb7VG7uVF5vj5Zde7tImgnhZE3R6YW/IACGIHb2ux+QkEXMhe591N+5jWOmL4Zw==} @@ -11292,6 +11319,9 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + encoding@0.1.12: + resolution: {integrity: sha512-bl1LAgiQc4ZWr++pNYUdRe/alecaHFeHxIJ/pNciqGdKXghaTCOwKkbKp6ye7pKZGu/GcaSXFk8PBVhgs+dJdA==} + encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -11860,6 +11890,9 @@ packages: fetch-cookie@3.1.0: resolution: {integrity: sha512-s/XhhreJpqH0ftkGVcQt8JE9bqk+zRn4jF5mPJXWZeQMCI5odV9K+wEWYbnzFPHgQZlvPSMjS4n4yawWE8RINw==} + fetch@1.1.0: + resolution: {integrity: sha512-5O8TwrGzoNblBG/jtK4NFuZwNCkZX6s5GfRNOaGtm+QGJEuNakSC/i2RW0R93KX6E0jVjNXm6O3CRN4Ql3K+yA==} + fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -13545,6 +13578,9 @@ packages: jpeg-js@0.3.7: resolution: {integrity: sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==} + js-base64-file@2.0.3: + resolution: {integrity: sha512-rcstfEIC1trslH4kZkHqiwBqy/jUf8SnmdV5FH6YzBQ2jhMOTIH5Mem6GcDP1sVcm5Lg/1JmS71m5jUPJGXolg==} + js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} @@ -18036,6 +18072,14 @@ packages: engines: {node: '>=4'} hasBin: true + strong-type@0.1.6: + resolution: {integrity: sha512-eJe5caH6Pi5oMMeQtIoBPpvNu/s4jiyb63u5tkHNnQXomK+puyQ5i+Z5iTLBr/xUz/pIcps0NSfzzFI34+gAXg==} + engines: {node: '>=12.0.0'} + + strong-type@1.1.0: + resolution: {integrity: sha512-X5Z6riticuH5GnhUyzijfDi1SoXas8ODDyN7K8lJeQK+Jfi4dKdoJGL4CXTskY/ATBcN+rz5lROGn1tAUkOX7g==} + engines: {node: '>=12.21.0'} + style-to-object@1.0.8: resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} @@ -19165,6 +19209,10 @@ packages: value-equal@1.0.1: resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==} + vanilla-test@1.4.9: + resolution: {integrity: sha512-QgmwI+b1RZ4xCePfezgNMfR8J5EWd0LnwmGcd3CFQWOOgH360Gww6U8RCk04TGSCn+W4oVB2biCqFc5mmYiJww==} + engines: {node: '>=12.21.0'} + varint@5.0.2: resolution: {integrity: sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==} @@ -19791,6 +19839,14 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} + xml2js@0.4.23: + resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -30527,6 +30583,8 @@ snapshots: dependencies: string-width: 4.2.3 + ansi-colors-es6@5.0.0: {} + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -31113,6 +31171,10 @@ snapshots: dependencies: '@noble/hashes': 1.3.0 + biskviit@1.0.1: + dependencies: + psl: 1.15.0 + bitcoinjs-lib@7.0.0-rc.0(typescript@5.6.3): dependencies: '@noble/hashes': 1.7.0 @@ -33187,6 +33249,16 @@ snapshots: dependencies: domelementtype: 2.3.0 + dominos@3.3.1(encoding@0.1.13): + dependencies: + js-base64-file: 2.0.3(encoding@0.1.13) + node-fetch: 2.7.0(encoding@0.1.13) + strong-type: 0.1.6 + vanilla-test: 1.4.9 + xml2js: 0.4.23 + transitivePeerDependencies: + - encoding + dompurify@3.2.2: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -33374,6 +33446,10 @@ snapshots: encodeurl@2.0.0: {} + encoding@0.1.12: + dependencies: + iconv-lite: 0.4.24 + encoding@0.1.13: dependencies: iconv-lite: 0.6.3 @@ -34282,6 +34358,11 @@ snapshots: set-cookie-parser: 2.7.1 tough-cookie: 5.0.0 + fetch@1.1.0: + dependencies: + biskviit: 1.0.1 + encoding: 0.1.12 + fflate@0.8.2: {} ffmpeg-static@5.2.0: @@ -36764,6 +36845,14 @@ snapshots: jpeg-js@0.3.7: {} + js-base64-file@2.0.3(encoding@0.1.13): + dependencies: + fetch: 1.1.0 + node-fetch: 2.7.0(encoding@0.1.13) + strong-type: 0.1.6 + transitivePeerDependencies: + - encoding + js-base64@3.7.7: {} js-git@0.7.8: @@ -42377,6 +42466,10 @@ snapshots: minimist: 1.2.8 through: 2.3.8 + strong-type@0.1.6: {} + + strong-type@1.1.0: {} + style-to-object@1.0.8: dependencies: inline-style-parser: 0.2.4 @@ -43607,6 +43700,11 @@ snapshots: value-equal@1.0.1: {} + vanilla-test@1.4.9: + dependencies: + ansi-colors-es6: 5.0.0 + strong-type: 1.1.0 + varint@5.0.2: {} varuint-bitcoin@2.0.0: @@ -44614,6 +44712,13 @@ snapshots: xml-name-validator@5.0.0: {} + xml2js@0.4.23: + dependencies: + sax: 1.4.1 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + xmlchars@2.2.0: {} xstream@11.14.0: From bfe6504358626d6b3d1103ca6f89f26c4e6985d3 Mon Sep 17 00:00:00 2001 From: Sayo <82053242+wtfsayo@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:35:26 +0530 Subject: [PATCH 10/11] test plugin build --- packages/plugin-dominos/src/actions/startOrder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-dominos/src/actions/startOrder.ts b/packages/plugin-dominos/src/actions/startOrder.ts index 2f80119e118..171358334a5 100644 --- a/packages/plugin-dominos/src/actions/startOrder.ts +++ b/packages/plugin-dominos/src/actions/startOrder.ts @@ -76,7 +76,7 @@ const handler: Handler = async ( }); try { - const orderDetails = (await generateObjectV2({ + const orderDetails = (await generateObject({ runtime, context, modelClass: ModelClass.LARGE, From 630c4fea9ff3552496510e8696e1a758fdf32bc6 Mon Sep 17 00:00:00 2001 From: Sayo <82053242+wtfsayo@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:47:20 +0530 Subject: [PATCH 11/11] add missing handler --- packages/plugin-dominos/src/PizzaOrderManager.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/plugin-dominos/src/PizzaOrderManager.ts b/packages/plugin-dominos/src/PizzaOrderManager.ts index 62156909c17..4e07fc0f004 100644 --- a/packages/plugin-dominos/src/PizzaOrderManager.ts +++ b/packages/plugin-dominos/src/PizzaOrderManager.ts @@ -782,4 +782,17 @@ export class PizzaOrderManager implements OrderManager { if (!order.items[0].toppings?.length) return "What toppings would you like?"; return "Would you like to add any more items to your order?"; } + + getNextRequiredAction(order: Order, customer: Customer): string { + if (!customer.name || !customer.phone || !customer.email || !customer.address) { + return "Customer information needs to be completed"; + } + if (order.paymentStatus !== PaymentStatus.VALID) { + return "Payment information needs to be provided"; + } + if (order.status === OrderStatus.PROCESSING) { + return "Order needs to be confirmed"; + } + return "No additional actions required"; + } }