@phantasy/sheesh-agent
Version:
AI Agent Framework with multi-provider support and real-time broadcasting
1,648 lines (1,633 loc) • 91.2 kB
JavaScript
'use strict';
var OpenAI = require('openai');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var OpenAI__default = /*#__PURE__*/_interopDefault(OpenAI);
// src/core/types.ts
var AIFrameworkError = class extends Error {
constructor(message, code, provider, details) {
super(message);
this.code = code;
this.provider = provider;
this.details = details;
this.name = "AIFrameworkError";
}
};
var ProviderError = class extends AIFrameworkError {
constructor(message, provider, details) {
super(message, "PROVIDER_ERROR", provider, details);
this.name = "ProviderError";
}
};
var ConfigurationError = class extends AIFrameworkError {
constructor(message, details) {
super(message, "CONFIGURATION_ERROR", void 0, details);
this.name = "ConfigurationError";
}
};
var RateLimitError = class extends AIFrameworkError {
constructor(message, provider, retryAfter) {
super(message, "RATE_LIMIT_ERROR", provider, { retryAfter });
this.name = "RateLimitError";
}
};
// src/core/base-provider.ts
var BaseProvider = class {
constructor(id, name, type) {
this.id = id;
this.name = name;
this.type = type;
}
config = {};
initialized = false;
async initialize(config) {
this.config = { ...this.config, ...config };
await this.onInitialize();
this.initialized = true;
}
async isAvailable() {
if (!this.initialized) {
return false;
}
try {
return await this.checkAvailability();
} catch {
return false;
}
}
async getUsage() {
if (!this.initialized) {
throw new Error(`Provider ${this.name} not initialized`);
}
return this.fetchUsage();
}
ensureInitialized() {
if (!this.initialized) {
throw new Error(`Provider ${this.name} not initialized. Call initialize() first.`);
}
}
};
// src/core/agent-controller.ts
var AgentController = class {
constructor(conversationService, initialPersonality) {
this.conversationService = conversationService;
this.state = {
personality: initialPersonality,
currentEmotion: "neutral",
energyLevel: 80,
tipGoals: [],
activeAnimations: [],
cameraPosition: { x: 0, y: 0, z: 5 },
lastInteraction: /* @__PURE__ */ new Date(),
conversationContext: []
};
}
state;
memory = /* @__PURE__ */ new Map();
actionQueue = [];
async processUserMessage(userId, message, metadata) {
this.updateUserMemory(userId, message);
const baseResponse = await this.conversationService.processMessage(
userId,
message,
this.state.personality,
{
...metadata,
agentState: this.getPublicState(),
userMemory: this.getUserMemory(userId)
}
);
const enhancedActions = await this.analyzeAndGenerateActions(message, baseResponse, userId);
this.updateAgentState(message, enhancedActions);
const { actions: _, ...responseWithoutActions } = baseResponse;
return {
...responseWithoutActions,
actions: enhancedActions
};
}
async analyzeAndGenerateActions(message, response, userId) {
const actions = [];
const lowerMessage = message.toLowerCase();
this.getUserMemory(userId);
if (lowerMessage.includes("dance") || lowerMessage.includes("move")) {
actions.push({
type: "vrm",
action: "animation",
value: "dance-basic",
duration: 5e3
});
}
if (lowerMessage.includes("smile") || response.emotion === "happy") {
actions.push({
type: "vrm",
action: "expression",
value: "smile",
duration: 3e3,
intensity: 0.8
});
}
if (lowerMessage.includes("wave") || lowerMessage.includes("hello")) {
actions.push({
type: "vrm",
action: "gesture",
value: "wave",
duration: 2e3
});
}
if (lowerMessage.includes("closer") || lowerMessage.includes("zoom")) {
actions.push({
type: "camera",
action: "zoom",
value: 3,
duration: 2e3,
easing: "ease-in-out"
});
}
if (lowerMessage.includes("face") || lowerMessage.includes("look at me")) {
actions.push({
type: "camera",
action: "preset",
value: "face-closeup",
duration: 1500
});
}
if (lowerMessage.includes("tip") || lowerMessage.includes("support")) {
actions.push({
type: "tip",
action: "request",
value: "Thank you for wanting to support me! Tips help me learn new animations!",
amount: 0.1,
currency: "SOL",
animation: "thank-you"
});
}
if (lowerMessage.includes("draw") || lowerMessage.includes("picture")) {
actions.push({
type: "canvas",
action: "draw",
value: "Generate a cute illustration based on our conversation",
position: { x: 0.7, y: 0.3 },
size: { width: 300, height: 300 }
});
}
if (response.emotion === "excited") {
actions.push({
type: "audio",
action: "play",
value: "excitement-ambient",
volume: 0.3,
fadeIn: 1e3
});
}
return actions;
}
updateUserMemory(userId, message) {
if (!this.memory.has(userId)) {
this.memory.set(userId, {
userId,
interactions: 0,
favoriteTopics: [],
tipHistory: [],
preferences: {}
});
}
const memory = this.memory.get(userId);
memory.interactions++;
memory.favoriteTopics = this.extractTopics(message);
}
updateAgentState(message, actions) {
this.state.lastInteraction = /* @__PURE__ */ new Date();
this.state.conversationContext.push(message);
if (this.state.conversationContext.length > 10) {
this.state.conversationContext = this.state.conversationContext.slice(-10);
}
this.state.energyLevel = Math.min(100, this.state.energyLevel + 2);
actions.forEach((action) => {
if (action.type === "vrm" && action.action === "animation") {
const animationValue = String(action.value);
this.state.activeAnimations.push(animationValue);
setTimeout(() => {
const index = this.state.activeAnimations.indexOf(animationValue);
if (index > -1) {
this.state.activeAnimations.splice(index, 1);
}
}, action.duration || 3e3);
}
});
}
extractTopics(message) {
const words = message.toLowerCase().split(" ");
const topics = ["dance", "music", "art", "games", "anime", "tech", "food"];
return topics.filter((topic) => words.includes(topic));
}
getUserMemory(userId) {
return this.memory.get(userId);
}
getPublicState() {
const { conversationContext: _conversationContext, ...publicState } = this.state;
return publicState;
}
addTipGoal(goal) {
const id = `goal-${Date.now()}`;
this.state.tipGoals.push({
...goal,
id,
currentAmount: 0,
isActive: true
});
return id;
}
updateTipGoal(goalId, amount) {
const goal = this.state.tipGoals.find((g) => g.id === goalId);
if (!goal) return false;
goal.currentAmount += amount;
if (goal.currentAmount >= goal.targetAmount) {
goal.isActive = false;
this.actionQueue.push({
type: "vrm",
action: "animation",
value: goal.animation || "celebration",
duration: 5e3
});
}
return true;
}
getActiveTipGoals() {
return this.state.tipGoals.filter((goal) => goal.isActive);
}
queueAction(action) {
this.actionQueue.push(action);
}
getNextAction() {
return this.actionQueue.shift();
}
updatePersonality(updates) {
this.state.personality = { ...this.state.personality, ...updates };
}
setEmotion(emotion) {
this.state.currentEmotion = emotion;
}
getCameraPosition() {
return this.state.cameraPosition;
}
setCameraPosition(position) {
this.state.cameraPosition = position;
}
};
var OpenAIText = class extends BaseProvider {
client;
constructor() {
super("openai-text", "OpenAI Text", "text");
}
async onInitialize() {
if (!this.config.apiKey) {
throw new ProviderError("OpenAI API key is required", this.id);
}
this.client = new OpenAI__default.default({
apiKey: this.config.apiKey,
baseURL: this.config.baseUrl,
timeout: this.config.timeout || 3e4,
maxRetries: this.config.maxRetries || 3
});
}
async checkAvailability() {
try {
const models = await this.client.models.list();
return models.data.length > 0;
} catch {
return false;
}
}
async fetchUsage() {
return {
tokensUsed: 0,
requestsCount: 0,
cost: 0
};
}
async generate(prompt, options) {
this.ensureInitialized();
const messages = [];
if (options?.systemPrompt) {
messages.push({ role: "system", content: options.systemPrompt });
}
messages.push({ role: "user", content: prompt });
return this.generateWithMessages(messages, options);
}
async generateWithMessages(messages, options) {
this.ensureInitialized();
try {
const completion = await this.client.chat.completions.create({
model: options?.model || "gpt-4-turbo-preview",
messages: messages.map((m) => ({
role: m.role,
content: m.content
})),
temperature: options?.temperature,
max_tokens: options?.maxTokens,
top_p: options?.topP,
frequency_penalty: options?.frequencyPenalty,
presence_penalty: options?.presencePenalty,
stop: options?.stop,
stream: false
});
const choice = completion.choices[0];
return {
text: choice.message.content || "",
model: completion.model,
usage: completion.usage ? {
promptTokens: completion.usage.prompt_tokens,
completionTokens: completion.usage.completion_tokens,
totalTokens: completion.usage.total_tokens
} : void 0,
finishReason: choice.finish_reason || void 0
};
} catch (error) {
if (error.status === 429) {
throw new ProviderError("Rate limit exceeded", this.id, {
retryAfter: error.headers?.["retry-after"]
});
}
throw new ProviderError(error.message || "Failed to generate text", this.id, error);
}
}
async *stream(prompt, options) {
this.ensureInitialized();
const messages = [];
if (options?.systemPrompt) {
messages.push({ role: "system", content: options.systemPrompt });
}
messages.push({ role: "user", content: prompt });
try {
const stream = await this.client.chat.completions.create({
model: options?.model || "gpt-4-turbo-preview",
messages: messages.map((m) => ({
role: m.role,
content: m.content
})),
temperature: options?.temperature,
max_tokens: options?.maxTokens,
top_p: options?.topP,
frequency_penalty: options?.frequencyPenalty,
presence_penalty: options?.presencePenalty,
stop: options?.stop,
stream: true
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) {
yield content;
}
}
} catch (error) {
throw new ProviderError(error.message || "Failed to stream text", this.id, error);
}
}
};
// src/providers/text/alkahest.ts
var AlkahestText = class extends BaseProvider {
baseUrl = "";
apiKey;
constructor() {
super("alkahest-text", "Alkahest Text", "text");
}
async onInitialize() {
this.baseUrl = this.config.baseUrl || "http://localhost:9999";
this.apiKey = this.config.apiKey;
if (!this.apiKey && !this.config.privyToken) {
throw new ProviderError("Alkahest API key or Privy token is required", this.id);
}
}
async checkAvailability() {
try {
const response = await fetch(`${this.baseUrl}/health`);
return response.ok;
} catch {
return false;
}
}
async fetchUsage() {
try {
const response = await fetch(`${this.baseUrl}/api/v1/mana/balance`, {
headers: this.getHeaders()
});
if (!response.ok) return { tokensUsed: 0, requestsCount: 0, cost: 0 };
const data = await response.json();
return {
remaining: data.balance,
tokensUsed: 0,
requestsCount: 0,
cost: 0
};
} catch {
return { tokensUsed: 0, requestsCount: 0, cost: 0 };
}
}
getHeaders() {
const headers = {
"Content-Type": "application/json"
};
if (this.apiKey) {
headers["Authorization"] = `Bearer ${this.apiKey}`;
} else if (this.config.privyToken) {
headers["Authorization"] = `Bearer ${this.config.privyToken}`;
}
return headers;
}
async generate(prompt, options) {
const messages = [];
if (options?.systemPrompt) {
messages.push({ role: "system", content: options.systemPrompt });
}
messages.push({ role: "user", content: prompt });
return this.generateWithMessages(messages, options);
}
async generateWithMessages(messages, options) {
this.ensureInitialized();
try {
const response = await fetch(`${this.baseUrl}/api/v1/chat/completions`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify({
model: options?.model || "gpt-4o",
messages: messages.map((m) => ({
role: m.role,
content: m.content
})),
stream: false,
temperature: options?.temperature,
max_tokens: options?.maxTokens,
top_p: options?.topP,
frequency_penalty: options?.frequencyPenalty,
presence_penalty: options?.presencePenalty,
stop: options?.stop
})
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: "Unknown error" }));
if (response.status === 429) {
const retryAfter = response.headers.get("X-RateLimit-Reset");
throw new RateLimitError(
"Rate limit exceeded",
this.id,
retryAfter ? parseInt(retryAfter) : void 0
);
}
if (response.status === 402) {
throw new ProviderError("Insufficient mana balance", this.id, error);
}
throw new ProviderError(
error.error?.message || error.error || "Failed to generate text",
this.id,
error
);
}
const data = await response.json();
const choice = data.choices[0];
return {
text: choice.message.content || "",
model: data.model,
usage: data.usage ? {
promptTokens: data.usage.prompt_tokens,
completionTokens: data.usage.completion_tokens,
totalTokens: data.usage.total_tokens
} : void 0,
finishReason: choice.finish_reason || void 0
};
} catch (error) {
if (error instanceof ProviderError || error instanceof RateLimitError) {
throw error;
}
throw new ProviderError(error.message || "Failed to generate text", this.id, error);
}
}
async *stream(prompt, options) {
const messages = [];
if (options?.systemPrompt) {
messages.push({ role: "system", content: options.systemPrompt });
}
messages.push({ role: "user", content: prompt });
yield* this.streamWithMessages(messages, options);
}
async *streamWithMessages(messages, options) {
this.ensureInitialized();
try {
const response = await fetch(`${this.baseUrl}/api/v1/chat/completions`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify({
model: options?.model || "gpt-4o",
messages: messages.map((m) => ({
role: m.role,
content: m.content
})),
stream: true,
temperature: options?.temperature,
max_tokens: options?.maxTokens,
top_p: options?.topP,
frequency_penalty: options?.frequencyPenalty,
presence_penalty: options?.presencePenalty,
stop: options?.stop
})
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: "Unknown error" }));
throw new ProviderError(
error.error?.message || error.error || "Failed to stream text",
this.id,
error
);
}
const reader = response.body?.getReader();
if (!reader) {
throw new ProviderError("No response body", this.id);
}
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]") return;
try {
const parsed = JSON.parse(data);
const content = parsed.choices[0]?.delta?.content;
if (content) {
yield content;
}
} catch (_e) {
}
}
}
}
} catch (error) {
if (error instanceof ProviderError) {
throw error;
}
throw new ProviderError(error.message || "Failed to stream text", this.id, error);
}
}
};
var MahoText = class extends BaseProvider {
client;
constructor() {
super("maho-text", "Maho Text", "text");
}
async onInitialize() {
if (!this.config.apiKey) {
throw new ProviderError("Maho API key is required", this.id);
}
this.client = new OpenAI__default.default({
apiKey: this.config.apiKey,
baseURL: this.config.baseUrl || "https://maho.sh/api/v1",
timeout: this.config.timeout || 3e4,
maxRetries: this.config.maxRetries || 3
});
}
async checkAvailability() {
try {
const models = await this.client.models.list();
return models.data.length > 0;
} catch {
return false;
}
}
async fetchUsage() {
return {
tokensUsed: 0,
requestsCount: 0,
cost: 0
};
}
async generate(prompt, options) {
this.ensureInitialized();
const messages = [];
if (options?.systemPrompt) {
messages.push({ role: "system", content: options.systemPrompt });
}
messages.push({ role: "user", content: prompt });
return this.generateWithMessages(messages, options);
}
async generateWithMessages(messages, options) {
this.ensureInitialized();
try {
const completion = await this.client.chat.completions.create({
model: options?.model || "gpt-3.5-turbo",
messages: messages.map((m) => ({
role: m.role,
content: m.content
})),
temperature: options?.temperature || 0.7,
max_tokens: options?.maxTokens || 2048,
top_p: options?.topP,
frequency_penalty: options?.frequencyPenalty,
presence_penalty: options?.presencePenalty,
stop: options?.stop,
stream: false
});
const choice = completion.choices[0];
return {
text: choice.message.content || "",
model: completion.model,
usage: completion.usage ? {
promptTokens: completion.usage.prompt_tokens,
completionTokens: completion.usage.completion_tokens,
totalTokens: completion.usage.total_tokens
} : void 0,
finishReason: choice.finish_reason || void 0
};
} catch (error) {
if (error.status === 429) {
throw new ProviderError("Rate limit exceeded", this.id, {
retryAfter: error.headers?.["retry-after"]
});
}
throw new ProviderError(error.message || "Failed to generate text", this.id, error);
}
}
async *stream(prompt, options) {
this.ensureInitialized();
const messages = [];
if (options?.systemPrompt) {
messages.push({ role: "system", content: options.systemPrompt });
}
messages.push({ role: "user", content: prompt });
try {
const stream = await this.client.chat.completions.create({
model: options?.model || "gpt-3.5-turbo",
messages: messages.map((m) => ({
role: m.role,
content: m.content
})),
temperature: options?.temperature || 0.7,
max_tokens: options?.maxTokens || 2048,
top_p: options?.topP,
frequency_penalty: options?.frequencyPenalty,
presence_penalty: options?.presencePenalty,
stop: options?.stop,
stream: true
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) {
yield content;
}
}
} catch (error) {
throw new ProviderError(error.message || "Failed to stream text", this.id, error);
}
}
};
var AkashText = class extends BaseProvider {
client;
constructor() {
super("akash-text", "Akash Text", "text");
}
async onInitialize() {
if (!this.config.apiKey) {
throw new ProviderError("Akash API key is required", this.id);
}
this.client = new OpenAI__default.default({
apiKey: this.config.apiKey,
baseURL: this.config.baseUrl || "https://chatapi.akash.network/api/v1",
timeout: this.config.timeout || 3e4,
maxRetries: this.config.maxRetries || 3
});
}
async checkAvailability() {
try {
const models = await this.client.models.list();
return models.data.length > 0;
} catch {
return false;
}
}
async fetchUsage() {
return {
tokensUsed: 0,
requestsCount: 0,
cost: 0
};
}
async generate(prompt, options) {
this.ensureInitialized();
const messages = [];
if (options?.systemPrompt) {
messages.push({ role: "system", content: options.systemPrompt });
}
messages.push({ role: "user", content: prompt });
return this.generateWithMessages(messages, options);
}
async generateWithMessages(messages, options) {
this.ensureInitialized();
try {
const completion = await this.client.chat.completions.create({
model: options?.model || "llama-3.1-8b-instruct",
messages: messages.map((m) => ({
role: m.role,
content: m.content
})),
temperature: options?.temperature || 0.7,
max_tokens: options?.maxTokens || 2048,
top_p: options?.topP,
frequency_penalty: options?.frequencyPenalty,
presence_penalty: options?.presencePenalty,
stop: options?.stop,
stream: false
});
const choice = completion.choices[0];
return {
text: choice.message.content || "",
model: completion.model,
usage: completion.usage ? {
promptTokens: completion.usage.prompt_tokens,
completionTokens: completion.usage.completion_tokens,
totalTokens: completion.usage.total_tokens
} : void 0,
finishReason: choice.finish_reason || void 0
};
} catch (error) {
if (error.status === 429) {
throw new ProviderError("Rate limit exceeded", this.id, {
retryAfter: error.headers?.["retry-after"]
});
}
throw new ProviderError(error.message || "Failed to generate text", this.id, error);
}
}
async *stream(prompt, options) {
this.ensureInitialized();
const messages = [];
if (options?.systemPrompt) {
messages.push({ role: "system", content: options.systemPrompt });
}
messages.push({ role: "user", content: prompt });
try {
const stream = await this.client.chat.completions.create({
model: options?.model || "llama-3.1-8b-instruct",
messages: messages.map((m) => ({
role: m.role,
content: m.content
})),
temperature: options?.temperature || 0.7,
max_tokens: options?.maxTokens || 2048,
top_p: options?.topP,
frequency_penalty: options?.frequencyPenalty,
presence_penalty: options?.presencePenalty,
stop: options?.stop,
stream: true
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) {
yield content;
}
}
} catch (error) {
throw new ProviderError(error.message || "Failed to stream text", this.id, error);
}
}
};
// src/core/provider-manager.ts
var ProviderManager = class {
constructor(config) {
this.config = config;
this.fallbackOrder = [config.primary, ...config.fallbacks];
}
providers = /* @__PURE__ */ new Map();
fallbackOrder = [];
async initialize() {
for (const providerName of this.fallbackOrder) {
const providerConfig = this.config.configs[providerName];
if (!providerConfig) continue;
let provider;
switch (providerName) {
case "maho":
provider = new MahoText();
break;
case "alkahest":
provider = new AlkahestText();
break;
case "akash":
provider = new AkashText();
break;
case "openai":
provider = new OpenAIText();
break;
default:
console.warn(`Unknown provider: ${providerName}`);
continue;
}
try {
await provider.initialize(providerConfig);
this.providers.set(providerName, provider);
console.log(`\u2705 Initialized ${providerName} provider`);
} catch (error) {
console.warn(`\u26A0\uFE0F Failed to initialize ${providerName}:`, error);
}
}
}
async generate(prompt, options) {
let lastError = null;
for (const providerName of this.fallbackOrder) {
const provider = this.providers.get(providerName);
if (!provider) continue;
try {
console.log(`\u{1F504} Trying ${providerName} provider...`);
const result = await provider.generate(prompt, options);
console.log(`\u2705 Success with ${providerName}`);
return result;
} catch (error) {
console.warn(`\u274C ${providerName} failed:`, error.message);
lastError = error;
if (error instanceof ProviderError && error.details?.retryAfter) {
await new Promise((resolve) => setTimeout(resolve, 1e3));
}
}
}
throw new ProviderError(
`All providers failed. Last error: ${lastError?.message}`,
"provider-manager",
lastError
);
}
async generateWithMessages(messages, options) {
let lastError = null;
for (const providerName of this.fallbackOrder) {
const provider = this.providers.get(providerName);
if (!provider) continue;
try {
console.log(`\u{1F504} Trying ${providerName} provider...`);
const result = await provider.generateWithMessages(messages, options);
console.log(`\u2705 Success with ${providerName}`);
return result;
} catch (error) {
console.warn(`\u274C ${providerName} failed:`, error.message);
lastError = error;
if (error instanceof ProviderError && error.details?.retryAfter) {
await new Promise((resolve) => setTimeout(resolve, 1e3));
}
}
}
throw new ProviderError(
`All providers failed. Last error: ${lastError?.message}`,
"provider-manager",
lastError
);
}
getAvailableProviders() {
return Array.from(this.providers.keys());
}
getProvider(name) {
return this.providers.get(name);
}
};
var OpenAITTS = class _OpenAITTS extends BaseProvider {
client;
static VOICES = [
{ id: "alloy", name: "Alloy", gender: "neutral" },
{ id: "echo", name: "Echo", gender: "male" },
{ id: "fable", name: "Fable", gender: "neutral" },
{ id: "onyx", name: "Onyx", gender: "male" },
{ id: "nova", name: "Nova", gender: "female" },
{ id: "shimmer", name: "Shimmer", gender: "female" }
];
constructor() {
super("openai-tts", "OpenAI TTS", "tts");
}
async onInitialize() {
if (!this.config.apiKey) {
throw new ProviderError("OpenAI API key is required", this.id);
}
this.client = new OpenAI__default.default({
apiKey: this.config.apiKey,
baseURL: this.config.baseUrl,
timeout: this.config.timeout || 6e4,
// Longer timeout for audio
maxRetries: this.config.maxRetries || 3
});
}
async checkAvailability() {
try {
const response = await this.client.audio.speech.create({
model: "tts-1",
voice: "alloy",
input: "test",
response_format: "mp3"
});
return response instanceof Response;
} catch {
return false;
}
}
async fetchUsage() {
return {
tokensUsed: 0,
requestsCount: 0,
cost: 0
};
}
async synthesize(text, options) {
this.ensureInitialized();
try {
const format = this.mapAudioFormat(options?.format || "mp3");
const response = await this.client.audio.speech.create({
model: options?.model || "tts-1",
voice: options?.voice || "alloy",
input: text,
response_format: format,
speed: options?.speed || 1
});
const arrayBuffer = await response.arrayBuffer();
return {
audio: arrayBuffer,
format: options?.format || "mp3",
voice: options?.voice || "alloy"
};
} catch (error) {
if (error.status === 429) {
throw new ProviderError("Rate limit exceeded", this.id, {
retryAfter: error.headers?.["retry-after"]
});
}
throw new ProviderError(error.message || "Failed to synthesize speech", this.id, error);
}
}
async getVoices() {
this.ensureInitialized();
return _OpenAITTS.VOICES;
}
async stream(text, options) {
this.ensureInitialized();
try {
const format = this.mapAudioFormat(options?.format || "mp3");
const response = await this.client.audio.speech.create({
model: options?.model || "tts-1",
voice: options?.voice || "alloy",
input: text,
response_format: format,
speed: options?.speed || 1
});
if (!response.body) {
throw new Error("No response body");
}
return response.body;
} catch (error) {
throw new ProviderError(error.message || "Failed to stream speech", this.id, error);
}
}
mapAudioFormat(format) {
switch (format) {
case "mp3":
case "opus":
case "aac":
case "flac":
return format;
case "wav":
case "ogg":
return "mp3";
default:
return "mp3";
}
}
};
// src/providers/tts/alkahest-tts.ts
var AlkahestTTS = class extends BaseProvider {
baseUrl = "";
apiKey;
voices = [];
constructor() {
super("alkahest-tts", "Alkahest TTS", "tts");
}
async onInitialize() {
this.baseUrl = this.config.baseUrl || "http://localhost:9999";
this.apiKey = this.config.apiKey;
if (!this.apiKey && !this.config.privyToken) {
throw new ProviderError("Alkahest API key or Privy token is required", this.id);
}
await this.fetchVoices();
}
async checkAvailability() {
try {
const response = await fetch(`${this.baseUrl}/api/v1/audio/voices`, {
headers: this.getHeaders()
});
return response.ok;
} catch {
return false;
}
}
async fetchUsage() {
try {
const response = await fetch(`${this.baseUrl}/api/v1/mana/balance`, {
headers: this.getHeaders()
});
if (!response.ok) return { tokensUsed: 0, requestsCount: 0, cost: 0 };
const data = await response.json();
return {
remaining: data.balance,
tokensUsed: 0,
requestsCount: 0,
cost: 0
};
} catch {
return { tokensUsed: 0, requestsCount: 0, cost: 0 };
}
}
getHeaders() {
const headers = {
"Content-Type": "application/json"
};
if (this.apiKey) {
headers["Authorization"] = `Bearer ${this.apiKey}`;
} else if (this.config.privyToken) {
headers["Authorization"] = `Bearer ${this.config.privyToken}`;
}
return headers;
}
async fetchVoices() {
try {
const response = await fetch(`${this.baseUrl}/api/v1/audio/voices`, {
headers: this.getHeaders()
});
if (response.ok) {
const data = await response.json();
this.voices = data.voices?.map((v) => ({
id: v.id || v.voice_id,
name: v.name,
gender: v.gender,
language: v.language || "en",
previewUrl: v.preview_url,
labels: v.labels || {}
})) || [];
}
} catch (error) {
console.warn("Failed to fetch voices:", error);
this.voices = [
{ id: "alloy", name: "Alloy", gender: "neutral" },
{ id: "echo", name: "Echo", gender: "male" },
{ id: "fable", name: "Fable", gender: "neutral" },
{ id: "onyx", name: "Onyx", gender: "male" },
{ id: "nova", name: "Nova", gender: "female" },
{ id: "shimmer", name: "Shimmer", gender: "female" }
];
}
}
async synthesize(text, options) {
this.ensureInitialized();
try {
const format = this.mapAudioFormat(options?.format || "mp3");
const response = await fetch(`${this.baseUrl}/api/v1/audio/speech`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify({
model: options?.model || "tts-1",
input: text,
voice: options?.voice || "alloy",
response_format: format,
speed: options?.speed || 1
})
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: "Unknown error" }));
if (response.status === 429) {
throw new ProviderError("Rate limit exceeded", this.id, {
retryAfter: response.headers.get("X-RateLimit-Reset")
});
}
if (response.status === 402) {
throw new ProviderError("Insufficient mana balance", this.id, error);
}
throw new ProviderError(
error.error?.message || error.error || "Failed to synthesize speech",
this.id,
error
);
}
const arrayBuffer = await response.arrayBuffer();
return {
audio: arrayBuffer,
format: options?.format || "mp3",
voice: options?.voice || "alloy"
};
} catch (error) {
if (error instanceof ProviderError) {
throw error;
}
throw new ProviderError(error.message || "Failed to synthesize speech", this.id, error);
}
}
async getVoices() {
this.ensureInitialized();
return this.voices;
}
async stream(text, options) {
this.ensureInitialized();
try {
const format = this.mapAudioFormat(options?.format || "mp3");
const response = await fetch(`${this.baseUrl}/api/v1/audio/speech`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify({
model: options?.model || "tts-1",
input: text,
voice: options?.voice || "alloy",
response_format: format,
speed: options?.speed || 1,
stream: true
// Enable streaming if supported
})
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: "Unknown error" }));
throw new ProviderError(
error.error?.message || error.error || "Failed to stream speech",
this.id,
error
);
}
if (!response.body) {
throw new Error("No response body");
}
return response.body;
} catch (error) {
if (error instanceof ProviderError) {
throw error;
}
throw new ProviderError(error.message || "Failed to stream speech", this.id, error);
}
}
mapAudioFormat(format) {
switch (format) {
case "mp3":
case "opus":
case "aac":
case "flac":
return format;
case "wav":
return "mp3";
case "ogg":
return "opus";
default:
return "mp3";
}
}
};
// src/providers/image/alkahest.ts
var AlkahestImage = class extends BaseProvider {
baseUrl = "";
apiKey;
constructor() {
super("alkahest-image", "Alkahest Image", "image");
}
async onInitialize() {
this.baseUrl = this.config.baseUrl || "http://localhost:9999";
this.apiKey = this.config.apiKey;
if (!this.apiKey && !this.config.privyToken) {
throw new ProviderError("Alkahest API key or Privy token is required", this.id);
}
}
async checkAvailability() {
try {
const response = await fetch(`${this.baseUrl}/api/v1/models`, {
headers: this.getHeaders()
});
if (!response.ok) return false;
const data = await response.json();
return data.data?.some(
(model) => model.id.includes("dall-e") || model.id.includes("stable-diffusion") || model.capabilities?.includes("image")
) || false;
} catch {
return false;
}
}
async fetchUsage() {
try {
const response = await fetch(`${this.baseUrl}/api/v1/mana/balance`, {
headers: this.getHeaders()
});
if (!response.ok) return { tokensUsed: 0, requestsCount: 0, cost: 0 };
const data = await response.json();
return {
remaining: data.balance,
tokensUsed: 0,
requestsCount: 0,
cost: 0
};
} catch {
return { tokensUsed: 0, requestsCount: 0, cost: 0 };
}
}
getHeaders() {
const headers = {
"Content-Type": "application/json"
};
if (this.apiKey) {
headers["Authorization"] = `Bearer ${this.apiKey}`;
} else if (this.config.privyToken) {
headers["Authorization"] = `Bearer ${this.config.privyToken}`;
}
return headers;
}
async generate(prompt, options) {
this.ensureInitialized();
try {
const response = await fetch(`${this.baseUrl}/api/v1/images/generations`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify({
prompt,
n: options?.n || 1,
size: options?.size || "1024x1024",
model: options?.model || "dall-e-3",
quality: options?.quality || "standard",
style: options?.style || "vivid"
})
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: "Unknown error" }));
if (response.status === 429) {
throw new ProviderError("Rate limit exceeded", this.id, {
retryAfter: response.headers.get("X-RateLimit-Reset")
});
}
if (response.status === 402) {
throw new ProviderError("Insufficient mana balance", this.id, error);
}
throw new ProviderError(
error.error?.message || error.error || "Failed to generate image",
this.id,
error
);
}
const data = await response.json();
return {
images: data.data.map((item) => ({
url: item.url,
base64: item.b64_json,
revisedPrompt: item.revised_prompt
})),
model: options?.model || "dall-e-3"
};
} catch (error) {
if (error instanceof ProviderError) {
throw error;
}
throw new ProviderError(error.message || "Failed to generate image", this.id, error);
}
}
};
// src/services/conversation.ts
var ConversationService = class {
constructor(textProvider, ttsProvider, options = {}) {
this.textProvider = textProvider;
this.ttsProvider = ttsProvider;
this.options = options;
}
conversations = /* @__PURE__ */ new Map();
async processMessage(userId, message, personality, metadata) {
const conversationId = `${userId}-${Date.now()}`;
let context = this.conversations.get(conversationId);
if (!context) {
context = {
conversationId,
userId,
messages: [],
metadata: metadata || {}
};
this.conversations.set(conversationId, context);
}
context.messages.push({
role: "user",
content: message,
timestamp: /* @__PURE__ */ new Date()
});
const messages = this.prepareMessages(context, personality);
const textOptions = {
temperature: this.options.temperature || 0.7,
maxTokens: 500
};
const textResponse = await this.textProvider.generateWithMessages(messages, textOptions);
context.messages.push({
role: "assistant",
content: textResponse.text,
timestamp: /* @__PURE__ */ new Date()
});
this.trimContext(context);
let audio;
if (this.options.enableTTS && this.ttsProvider) {
const ttsOptions = {
voice: metadata?.voice || "nova",
speed: 1
};
try {
const audioResponse = await this.ttsProvider.synthesize(textResponse.text, ttsOptions);
audio = audioResponse.audio;
} catch (error) {
console.error("TTS generation failed:", error);
}
}
const actions = this.options.enableActions ? this.extractActions(textResponse.text) : void 0;
const emotion = this.detectEmotion(textResponse.text);
return {
text: textResponse.text,
audio,
emotion,
actions,
metadata: {
conversationId,
model: textResponse.model,
usage: textResponse.usage
}
};
}
prepareMessages(context, personality) {
const messages = [];
if (personality) {
messages.push({
role: "system",
content: this.buildPersonalityPrompt(personality)
});
}
const maxMessages = this.options.maxContextMessages || 10;
const recentMessages = context.messages.slice(-maxMessages);
messages.push(...recentMessages);
return messages;
}
buildPersonalityPrompt(personality) {
let prompt = `You are ${personality.name}. ${personality.description}
`;
prompt += `Your personality traits: ${personality.traits.join(", ")}.
`;
prompt += `Communication tone: ${personality.tone}.
`;
if (personality.guidelines.length > 0) {
prompt += `
Guidelines:
`;
personality.guidelines.forEach((guideline) => {
prompt += `- ${guideline}
`;
});
}
if (personality.examples && personality.examples.length > 0) {
prompt += `
Example interactions:
`;
personality.examples.forEach((example) => {
prompt += `User: ${example.input}
`;
prompt += `You: ${example.output}
`;
});
}
return prompt;
}
trimContext(context) {
const maxMessages = (this.options.maxContextMessages || 10) * 2;
if (context.messages.length > maxMessages) {
const systemMessages = context.messages.filter((m) => m.role === "system");
const recentMessages = context.messages.slice(-maxMessages);
context.messages = [...systemMessages, ...recentMessages];
}
}
extractActions(text) {
const actions = [];
if (text.includes("*waves*") || text.includes("*waving*")) {
actions.push({
type: "animation",
value: "wave",
duration: 2e3
});
}
if (text.includes("*dances*") || text.includes("*dancing*")) {
actions.push({
type: "animation",
value: "dance",
duration: 5e3
});
}
if (text.includes(":)") || text.includes("\u{1F60A}")) {
actions.push({
type: "expression",
value: "happy",
duration: 3e3
});
}
return actions.length > 0 ? actions : void 0;
}
detectEmotion(text) {
const emotions = {
happy: ["happy", "joy", "excited", "great", "wonderful", "\u{1F60A}", ":)"],
sad: ["sad", "sorry", "disappointed", "unfortunate", "\u{1F622}", ":("],
angry: ["angry", "mad", "frustrated", "annoyed", "\u{1F620}"],
surprised: ["wow", "amazing", "surprised", "unexpected", "\u{1F62E}"],
neutral: ["okay", "alright", "understand", "got it"]
};
const lowerText = text.toLowerCase();
for (const [emotion, keywords] of Object.entries(emotions)) {
if (keywords.some((keyword) => lowerText.includes(keyword))) {
return emotion;
}
}
return "neutral";
}
clearConversation(conversationId) {
this.conversations.delete(conversationId);
}
getConversation(conversationId) {
return this.conversations.get(conversationId);
}
};
// src/services/tip-management.ts
var TipManagementService = class {
goals = /* @__PURE__ */ new Map();
transactions = /* @__PURE__ */ new Map();
milestones = [];
tipperStats = /* @__PURE__ */ new Map();
totalEarned = { SOL: 0, NEAR: 0, ETH: 0, USD: 0 };
constructor() {
this.initializeDefaultMilestones();
}
initializeDefaultMilestones() {
this.milestones = [
{
amount: 1,
currency: "SOL",
reward: { type: "animation", value: "thank-you-wave", duration: 3e3 },
message: "Thank you for your first tip! \u{1F496}",
achieved: false
},
{
amount: 5,
currency: "SOL",
reward: { type: "animation", value: "dance-happy", duration: 5e3 },
message: "Wow! You are amazing! Time to dance! \u{1F483}",
achieved: false
},
{
amount: 10,
currency: "SOL",
reward: {
type: "unlock",
value: "special-animations",
unlocks: ["dance-special", "pose-cute", "gesture-heart"],
message: "You unlocked special animations! \u2728"
},
message: "Special animations unlocked! You are incredible! \u{1F31F}",
achieved: false
},
{
amount: 25,
currency: "SOL",
reward: { type: "content", value: "custom-image-generation" },
message: "Custom images unlocked! I can draw anything for you! \u{1F3A8}",
achieved: false
},
{
amount: 50,
currency: "SOL",
reward: {
type: "special",
value: "vip-access",
unlocks: ["private-chat", "custom-personality", "priority-responses"]
},
message: "VIP access granted! You are truly special! \u{1F451}",
achieved: false
}
];
}
createGoal(goal) {
const id = `goal_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const newGoal = {
...goal,
id,
currentAmount: 0,
createdAt: /* @__PURE__ */ new Date()
};
this.goals.set(id, newGoal);
return id;
}
processTip(userId, amount, currency, goalId, message) {
const transactionId = `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const transaction = {
id: transactionId,
userId,
amount,
currency,
goalId,
message,
timestamp: /* @__PURE__ */ new Date(),
status: "confirmed"
// In real implementation, this would be 'pending' initially
};
this.transactions.set(transactionId, transaction);
if (currency in this.totalEarned) {
this.totalEarned[currency] += amount;
}
this.updateTipperStats(userId, amount, currency);
const actions = [];
const achievedMilestones = [];
let goalCompleted;
if (goalId && this.goals.has(goalId)) {
const goal = this.goals.get(goalId);
goal.currentAmount += amount;
if (goal.currentAmount >= goal.targetAmount && goal.isActive) {
goal.isActive = false;
goal.completedAt = /* @__PURE__ */ new Date();
goalCompleted = goal;
actions.push(this.createRewardAction(goal.reward));
if (goal.reward.message) {
actions.push({
type: "audio",
action: "play",
value: "goal-completion-sound",
volume: 0.8
});
}
}
}
const userStats = this.tipperStats.get(userId);
if (userStats) {
this.milestones.forEach((milestone) => {
if (!milestone.achieved && userStats.totalTipped >= milestone.amount && userStats.favoriteCurrency === milestone.currency) {
milestone.achieved = true;
milestone.achievedAt = /* @__PURE__ */ new Date();
achievedMilestones.push(milestone);
actions.push(this.createRewardAction(milestone.reward));
}
});
}
actions.push({
type: "vrm",
action: "expression",
value: "smile-big",
duration: 3e3,
intensity: 0.9
});
actions.push({
type: "vrm",
action: "gesture",
value: "heart-hands",
duration: 2e3
});
if (amount >= 5) {
actions.push({
type: "canvas",
action: "effect",
value: "hearts",
duration: 5e3
});
}
if (amount >= 10) {
actions.push({
type: "vrm",
action: "animation",
value: "dance-celebration",
duration: 8e3
});
}
return {
success: true,
actions,
milestones: achievedMilestones,
goalCompleted
};
}
createRewardAction(reward) {
switch (reward.type) {
case "animation":
return {
type: "vrm",
action: "animation",
value: reward.value,
duration: reward.duration || 5e3
};
case "content":
return {
type: "canvas",
action: "draw",
value: reward.value,
position: { x: 0.7, y: 0.3 },
size: { width: 400, height: 400 }
};
case "special":
return {
type: "audio",
action: "play",
value: "special-unlock-sound",
volume: 0.7
};
case "unlock":
return {
type: "canvas",
action: "overlay",
value: "unlock-notification",
duration: 5e3
};
default:
return {