diff --git a/biome.json b/biome.json index 7772669d21d..125fb2ecf25 100644 --- a/biome.json +++ b/biome.json @@ -8,17 +8,13 @@ "rules": { "recommended": true, "suspicious": { - "noExplicitAny": "warn", "noArrayIndexKey": "warn", "noPrototypeBuiltins": "warn", "noDuplicateObjectKeys": "warn", "noGlobalIsNan": "warn", - "noDuplicateFontNames": "warn", "noSelfCompare": "warn", "noDoubleEquals": "warn", - "noImplicitAnyLet": "warn", "noAssignInExpressions": "warn", - "noExportsInTest": "warn", "noConstEnum": "warn", "noEmptyInterface": "warn" }, @@ -30,42 +26,15 @@ "noUnnecessaryContinue": "warn", "noInnerDeclarations": "warn" }, - "style": { - "useConst": "warn", - "useTemplate": "warn", - "useImportType": "warn", - "useNodejsImportProtocol": "warn", - "noUselessElse": "warn", - "useSelfClosingElements": "warn", - "useNumberNamespace": "warn", - "noUnusedTemplateLiteral": "warn", - "noInferrableTypes": "warn", - "noNonNullAssertion": "warn", - "noParameterAssign": "warn", - "useDefaultParameterLast": "warn", - "useExponentiationOperator": "warn", - "noVar": "warn", - "useSingleVarDeclarator": "warn", - "useExportType": "warn" - }, - "a11y": { - "useAltText": "warn", - "useFocusableInteractive": "warn", - "useMediaCaption": "warn", - "noSvgWithoutTitle": "warn", - "useKeyWithClickEvents": "warn" - }, "complexity": { "noForEach": "warn", "useOptionalChain": "warn", "useArrowFunction": "warn", "useFlatMap": "warn", - "useLiteralKeys": "warn", "noBannedTypes": "warn", "noStaticOnlyClass": "warn", "noThisInStatic": "warn", "noUselessConstructor": "warn", - "noUselessTernary": "warn", "noUselessSwitchCase": "warn", "noUselessCatch": "warn" }, diff --git a/package.json b/package.json index 5b997951194..c3d96d8a148 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "scripts": { "format": "biome format --write .", "cli": "bun --filter=@elizaos/cli cli", - "lint": "biome lint . --write", + "lint": "turbo run lint:fix --filter=./packages/*", "check": "biome check --apply .", "preinstall": "npx only-allow bun", "build": "turbo run build --filter=./packages/core && turbo run build --filter=./packages/*", diff --git a/packages/agent/src/api.ts b/packages/agent/src/api.ts index 84872a1e627..bfee1123210 100644 --- a/packages/agent/src/api.ts +++ b/packages/agent/src/api.ts @@ -1,11 +1,11 @@ import { type Character, getEnvVariable, - IAgentRuntime, - ITeeLogService, + type IAgentRuntime, + type ITeeLogService, logger, ServiceType, - TeeLogQuery, + type TeeLogQuery, type UUID, validateCharacterConfig, validateUuid diff --git a/packages/agent/src/defaultCharacter.ts b/packages/agent/src/defaultCharacter.ts index f74679419a2..2f3a570de06 100644 --- a/packages/agent/src/defaultCharacter.ts +++ b/packages/agent/src/defaultCharacter.ts @@ -1,4 +1,4 @@ -import { type Character } from "@elizaos/core"; +import type { Character } from "@elizaos/core"; export const defaultCharacter: Character = { name: "Eliza", @@ -6,6 +6,7 @@ export const defaultCharacter: Character = { plugins: [ "@elizaos/plugin-anthropic", "@elizaos/plugin-openai", + "@elizaos/plugin-local-ai", "@elizaos/plugin-discord", "@elizaos/plugin-node", "@elizaos/plugin-telegram", diff --git a/packages/agent/src/plugins.test.ts b/packages/agent/src/plugins.test.ts index 460d1bbb086..16618050955 100644 --- a/packages/agent/src/plugins.test.ts +++ b/packages/agent/src/plugins.test.ts @@ -8,7 +8,7 @@ import { DbCacheAdapter, logger, stringToUuid, - TestSuite, + type TestSuite, type IAgentRuntime } from "@elizaos/core"; import { afterAll, beforeAll, describe, it } from 'vitest'; diff --git a/packages/agent/src/reply.ts b/packages/agent/src/reply.ts index 61d91e65af1..d59be455e80 100644 --- a/packages/agent/src/reply.ts +++ b/packages/agent/src/reply.ts @@ -1,10 +1,10 @@ -import { - type Action, - type ActionExample, - type HandlerCallback, - type IAgentRuntime, - type Memory, - type State, +import type { + Action, + ActionExample, + HandlerCallback, + IAgentRuntime, + Memory, + State, } from "@elizaos/core"; const replyAction = { diff --git a/packages/cli/src/commands/agent.ts b/packages/cli/src/commands/agent.ts index 1ebcb5a5808..91a00467ae8 100644 --- a/packages/cli/src/commands/agent.ts +++ b/packages/cli/src/commands/agent.ts @@ -62,7 +62,7 @@ agent try { // If input is a number, get agent ID from index if (!isNaN(Number(agentIdOrIndex))) { - agentIdOrIndex = await getAgentIdFromIndex(parseInt(agentIdOrIndex)) + agentIdOrIndex = await getAgentIdFromIndex(Number.parseInt(agentIdOrIndex)) } const response = await fetch(`${AGENT_RUNTIME_URL}/agents/${agentIdOrIndex}`) @@ -122,7 +122,7 @@ agent .action(async (agentIdOrIndex) => { try { if (!isNaN(Number(agentIdOrIndex))) { - agentIdOrIndex = await getAgentIdFromIndex(parseInt(agentIdOrIndex)) + agentIdOrIndex = await getAgentIdFromIndex(Number.parseInt(agentIdOrIndex)) } const response = await fetch(`${AGENT_RUNTIME_URL}/agents/${agentIdOrIndex}/stop`, { @@ -146,7 +146,7 @@ agent .action(async (agentIdOrIndex) => { try { if (!isNaN(Number(agentIdOrIndex))) { - agentIdOrIndex = await getAgentIdFromIndex(parseInt(agentIdOrIndex)) + agentIdOrIndex = await getAgentIdFromIndex(Number.parseInt(agentIdOrIndex)) } const response = await fetch(`${AGENT_RUNTIME_URL}/agents/${agentIdOrIndex}`, { @@ -171,7 +171,7 @@ agent .action(async (agentIdOrIndex, configPath) => { try { if (!isNaN(Number(agentIdOrIndex))) { - agentIdOrIndex = await getAgentIdFromIndex(parseInt(agentIdOrIndex)) + agentIdOrIndex = await getAgentIdFromIndex(Number.parseInt(agentIdOrIndex)) } const config = JSON.parse(fs.readFileSync(configPath, 'utf8')) diff --git a/packages/core/__tests__/knowledge.test.ts b/packages/core/__tests__/knowledge.test.ts index 299394924f7..a6a2919ba62 100644 --- a/packages/core/__tests__/knowledge.test.ts +++ b/packages/core/__tests__/knowledge.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import knowledge from "../src/knowledge"; import type { AgentRuntime } from "../src/runtime"; -import { type Memory } from "../src/types"; +import type { Memory } from "../src/types"; vi.mock("../generation", () => ({ splitChunks: vi.fn().mockImplementation(async (text) => [text]), diff --git a/packages/core/__tests__/mockCharacter.ts b/packages/core/__tests__/mockCharacter.ts index 52aac1434b8..bf7043c30c2 100644 --- a/packages/core/__tests__/mockCharacter.ts +++ b/packages/core/__tests__/mockCharacter.ts @@ -1,4 +1,4 @@ -import { type Character } from "@elizaos/core"; +import type { Character } from "@elizaos/core"; export const mockCharacter: Character = { name: "Eliza", diff --git a/packages/core/__tests__/runtime.test.ts b/packages/core/__tests__/runtime.test.ts index dd4eb2a7f32..b418c531d9c 100644 --- a/packages/core/__tests__/runtime.test.ts +++ b/packages/core/__tests__/runtime.test.ts @@ -1,11 +1,11 @@ import { beforeEach, describe, expect, it, test, vi } from "vitest"; import { AgentRuntime } from "../src/runtime"; -import { - type Action, - type IDatabaseAdapter, - type IMemoryManager, - type Memory, - type UUID +import type { + Action, + IDatabaseAdapter, + IMemoryManager, + Memory, + UUID } from "../src/types"; // Mock dependencies with minimal implementations diff --git a/packages/core/src/knowledge.ts b/packages/core/src/knowledge.ts index 9a149d7455d..8b157646fa8 100644 --- a/packages/core/src/knowledge.ts +++ b/packages/core/src/knowledge.ts @@ -8,6 +8,7 @@ async function get( runtime: AgentRuntime, message: Memory ): Promise { + console.log("get", message); // Add validation for message if (!message?.content?.text) { logger.warn("Invalid message for knowledge query:", { @@ -69,6 +70,7 @@ async function set( chunkSize = 512, bleed = 20 ) { + console.log("set", item); const embedding = await runtime.useModel(ModelClass.TEXT_EMBEDDING, null); await runtime.documentsManager.createMemory({ id: item.id, diff --git a/packages/core/src/memory.ts b/packages/core/src/memory.ts index 08ea3e13e6e..bda44b96f7b 100644 --- a/packages/core/src/memory.ts +++ b/packages/core/src/memory.ts @@ -50,6 +50,7 @@ export class MemoryManager implements IMemoryManager { * @throws Error if the memory content is empty */ async addEmbeddingToMemory(memory: Memory): Promise { + console.log("addEmbeddingToMemory", memory); // Return early if embedding already exists if (memory.embedding) { return memory; @@ -173,6 +174,7 @@ export class MemoryManager implements IMemoryManager { */ async createMemory(memory: Memory, unique = false): Promise { // TODO: check memory.agentId == this.runtime.agentId + console.log("createMemory", memory); const existingMessage = await this.runtime.databaseAdapter.getMemoryById(memory.id); diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 5ebe34f90cd..4bcd1e6d798 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -44,8 +44,8 @@ import { type Provider, type State, type UUID, - ServiceType, - Service + type ServiceType, + type Service } from "./types.ts"; import { stringToUuid } from "./uuid.ts"; diff --git a/packages/core/src/test_resources/createRuntime.ts b/packages/core/src/test_resources/createRuntime.ts index 62b183ba06d..21beebe4d21 100644 --- a/packages/core/src/test_resources/createRuntime.ts +++ b/packages/core/src/test_resources/createRuntime.ts @@ -1,7 +1,7 @@ import { SqliteDatabaseAdapter, loadVecExtensions } from "@elizaos-plugins/sqlite"; import type { DatabaseAdapter } from "../database.ts"; import { AgentRuntime } from "../runtime.ts"; -import { type Action, type Evaluator, type Provider } from "../types.ts"; +import type { Action, Evaluator, Provider } from "../types.ts"; import { zeroUuid } from "./constants.ts"; diff --git a/packages/plugin-anthropic/biome.json b/packages/plugin-anthropic/biome.json new file mode 100644 index 00000000000..d7e0349301a --- /dev/null +++ b/packages/plugin-anthropic/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-anthropic/src/index.ts b/packages/plugin-anthropic/src/index.ts index 777b68675b4..9c3418566c1 100644 --- a/packages/plugin-anthropic/src/index.ts +++ b/packages/plugin-anthropic/src/index.ts @@ -1,7 +1,7 @@ import { anthropic } from "@ai-sdk/anthropic"; import type { Plugin } from "@elizaos/core"; import { - GenerateTextParams, + type GenerateTextParams, ModelClass } from "@elizaos/core"; import { generateText } from "ai"; diff --git a/packages/plugin-bootstrap/biome.json b/packages/plugin-bootstrap/biome.json new file mode 100644 index 00000000000..d7e0349301a --- /dev/null +++ b/packages/plugin-bootstrap/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-bootstrap/src/providers/facts.ts b/packages/plugin-bootstrap/src/providers/facts.ts index 4e6aa0f3831..6bd2eac5c2c 100644 --- a/packages/plugin-bootstrap/src/providers/facts.ts +++ b/packages/plugin-bootstrap/src/providers/facts.ts @@ -1,7 +1,7 @@ import type { Memory, Provider, State } from "@elizaos/core"; import { formatMessages, - IAgentRuntime, + type IAgentRuntime, MemoryManager, ModelClass } from "@elizaos/core"; @@ -10,6 +10,8 @@ import { formatFacts } from "../evaluators/fact.ts"; const factsProvider: Provider = { get: async (runtime: IAgentRuntime, message: Memory, state?: State) => { const recentMessagesData = state?.recentMessagesData?.slice(-10); + console.log("get", message); + const recentMessages = formatMessages({ messages: recentMessagesData, diff --git a/packages/plugin-discord/biome.json b/packages/plugin-discord/biome.json new file mode 100644 index 00000000000..d7e0349301a --- /dev/null +++ b/packages/plugin-discord/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-discord/src/actions/reply.ts b/packages/plugin-discord/src/actions/reply.ts index bb887919a06..98aff89e52c 100644 --- a/packages/plugin-discord/src/actions/reply.ts +++ b/packages/plugin-discord/src/actions/reply.ts @@ -1,9 +1,9 @@ -import { - type Action, - type HandlerCallback, - type IAgentRuntime, - type Memory, - type State +import type { + Action, + HandlerCallback, + IAgentRuntime, + Memory, + State } from "@elizaos/core"; const replyAction = { diff --git a/packages/plugin-discord/src/index.ts b/packages/plugin-discord/src/index.ts index 533ed7fe405..d9ffd9e08dd 100644 --- a/packages/plugin-discord/src/index.ts +++ b/packages/plugin-discord/src/index.ts @@ -1,7 +1,7 @@ import { logger, stringToUuid, - TestSuite, + type TestSuite, type Character, type Client as ElizaClient, type IAgentRuntime, @@ -28,7 +28,7 @@ import { MessageManager } from "./messages.ts"; import channelStateProvider from "./providers/channelState.ts"; import voiceStateProvider from "./providers/voiceState.ts"; import reply from "./actions/reply.ts"; -import { IDiscordClient } from "./types.ts"; +import type { IDiscordClient } from "./types.ts"; import { VoiceManager } from "./voice.ts"; export class DiscordClient extends EventEmitter implements IDiscordClient { diff --git a/packages/plugin-discord/src/messages.ts b/packages/plugin-discord/src/messages.ts index f3742f1fe7b..84e5205c7bf 100644 --- a/packages/plugin-discord/src/messages.ts +++ b/packages/plugin-discord/src/messages.ts @@ -9,7 +9,7 @@ import { ChannelType, type Client, type Message as DiscordMessage, - TextChannel, + type TextChannel, } from "discord.js"; import { AttachmentManager } from "./attachments.ts"; import { diff --git a/packages/plugin-discord/src/types.ts b/packages/plugin-discord/src/types.ts index bffdc28c665..ec45a8243d0 100644 --- a/packages/plugin-discord/src/types.ts +++ b/packages/plugin-discord/src/types.ts @@ -1,8 +1,8 @@ -import { - type Character, - type IAgentRuntime +import type { + Character, + IAgentRuntime } from "@elizaos/core"; -import { +import type { Client } from "discord.js"; diff --git a/packages/plugin-drizzle/biome.json b/packages/plugin-drizzle/biome.json new file mode 100644 index 00000000000..d7e0349301a --- /dev/null +++ b/packages/plugin-drizzle/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-drizzle/src/index.ts b/packages/plugin-drizzle/src/index.ts index ef0134a9e4d..a8091b581f5 100644 --- a/packages/plugin-drizzle/src/index.ts +++ b/packages/plugin-drizzle/src/index.ts @@ -4,7 +4,7 @@ import { DatabaseAdapter, type GoalStatus, type Participant, - elizaLogger, + logger, type Goal, type IDatabaseCacheAdapter, type Memory, @@ -33,10 +33,10 @@ import { knowledgeTable, cacheTable, } from "./schema"; -import { drizzle, NodePgDatabase } from "drizzle-orm/node-postgres"; +import { drizzle, type NodePgDatabase } from "drizzle-orm/node-postgres"; import { v4 } from "uuid"; import { runMigrations } from "./migrations"; -import pg, { ConnectionConfig, PoolConfig } from "pg"; +import pg, { type ConnectionConfig, type PoolConfig } from "pg"; type Pool = pg.Pool; export class DrizzleDatabaseAdapter @@ -67,7 +67,7 @@ export class DrizzleDatabaseAdapter this.pool = new pg.Pool(poolConfig); this.pool.on("error", (err) => { - elizaLogger.error("Unexpected pool error", err); + logger.error("Unexpected pool error", err); this.handlePoolError(err); }); @@ -92,7 +92,7 @@ export class DrizzleDatabaseAdapter } private async handlePoolError(error: Error) { - elizaLogger.error("Pool error occurred, attempting to reconnect", { + logger.error("Pool error occurred, attempting to reconnect", { error: error.message, }); @@ -107,9 +107,9 @@ export class DrizzleDatabaseAdapter }); await this.testConnection(); - elizaLogger.success("Pool reconnection successful"); + logger.success("Pool reconnection successful"); } catch (reconnectError) { - elizaLogger.error("Failed to reconnect pool", { + logger.error("Failed to reconnect pool", { error: reconnectError instanceof Error ? reconnectError.message @@ -138,7 +138,7 @@ export class DrizzleDatabaseAdapter if (attempt < this.maxRetries) { // Calculate delay with exponential backoff const backoffDelay = Math.min( - this.baseDelay * Math.pow(2, attempt - 1), + this.baseDelay * (2 ** (attempt - 1)), this.maxDelay ); @@ -146,7 +146,7 @@ export class DrizzleDatabaseAdapter const jitter = Math.random() * this.jitterMax; const delay = backoffDelay + jitter; - elizaLogger.warn( + logger.warn( `Database operation failed (attempt ${attempt}/${this.maxRetries}):`, { error: @@ -159,7 +159,7 @@ export class DrizzleDatabaseAdapter await new Promise((resolve) => setTimeout(resolve, delay)); } else { - elizaLogger.error("Max retry attempts reached:", { + logger.error("Max retry attempts reached:", { error: error instanceof Error ? error.message @@ -179,9 +179,9 @@ export class DrizzleDatabaseAdapter async cleanup(): Promise { try { await this.pool.end(); - elizaLogger.info("Database pool closed"); + logger.info("Database pool closed"); } catch (error) { - elizaLogger.error("Error closing database pool:", error); + logger.error("Error closing database pool:", error); } } @@ -221,13 +221,13 @@ export class DrizzleDatabaseAdapter const hasVector = vectorExt?.rows.length > 0; if (!hasVector) { - elizaLogger.warn("Vector extension not found"); + logger.warn("Vector extension not found"); return false; } return true; } catch (error) { - elizaLogger.error("Error validating vector setup:", error); + logger.error("Error validating vector setup:", error); return false; } } @@ -248,7 +248,7 @@ export class DrizzleDatabaseAdapter await runMigrations(this.pool); } } catch (error) { - elizaLogger.error("Failed to initialize database:", error); + logger.error("Failed to initialize database:", error); throw error; } } @@ -259,7 +259,7 @@ export class DrizzleDatabaseAdapter await (this.db as any).client.close(); } } catch (error) { - elizaLogger.error("Failed to close database connection:", { + logger.error("Failed to close database connection:", { error: error instanceof Error ? error.message : String(error), }); throw error; @@ -269,13 +269,13 @@ export class DrizzleDatabaseAdapter async testConnection(): Promise { try { const result = await this.db.execute(sql`SELECT NOW()`); - elizaLogger.success( + logger.success( "Database connection test successful:", result.rows[0] ); return true; } catch (error) { - elizaLogger.error("Database connection test failed:", error); + logger.error("Database connection test failed:", error); throw new Error( `Failed to connect to database: ${(error as Error).message}` ); @@ -319,13 +319,13 @@ export class DrizzleDatabaseAdapter details: sql`${account.details}::jsonb` || {}, }); - elizaLogger.debug("Account created successfully:", { + logger.debug("Account created successfully:", { accountId, }); return true; } catch (error) { - elizaLogger.error("Error creating account:", { + logger.error("Error creating account:", { error: error instanceof Error ? error.message : String(error), accountId: account.id, @@ -547,7 +547,7 @@ export class DrizzleDatabaseAdapter })) .filter((row) => Array.isArray(row.embedding)); } catch (error) { - elizaLogger.error("Error in getCachedEmbeddings:", { + logger.error("Error in getCachedEmbeddings:", { error: error instanceof Error ? error.message : String(error), tableName: opts.query_table_name, @@ -580,7 +580,7 @@ export class DrizzleDatabaseAdapter type: params.type, }); } catch (error) { - elizaLogger.error("Failed to create log entry:", { + logger.error("Failed to create log entry:", { error: error instanceof Error ? error.message : String(error), type: params.type, @@ -614,7 +614,7 @@ export class DrizzleDatabaseAdapter .where(eq(participantTable.roomId, params.roomId)) .orderBy(accountTable.name); - elizaLogger.debug("Retrieved actor details:", { + logger.debug("Retrieved actor details:", { roomId: params.roomId, actorCount: result.length, }); @@ -637,7 +637,7 @@ export class DrizzleDatabaseAdapter }, }; } catch (error) { - elizaLogger.warn("Failed to parse actor details:", { + logger.warn("Failed to parse actor details:", { actorId: row.id, error: error instanceof Error @@ -658,7 +658,7 @@ export class DrizzleDatabaseAdapter } }); } catch (error) { - elizaLogger.error("Failed to fetch actor details:", { + logger.error("Failed to fetch actor details:", { roomId: params.roomId, error: error instanceof Error ? error.message : String(error), @@ -778,7 +778,7 @@ export class DrizzleDatabaseAdapter async createMemory(memory: Memory, tableName: string): Promise { return this.withDatabase(async () => { - elizaLogger.debug("DrizzleAdapter createMemory:", { + logger.debug("DrizzleAdapter createMemory:", { memoryId: memory.id, embeddingLength: memory.embedding?.length, contentLength: memory.content?.text?.length, @@ -786,7 +786,7 @@ export class DrizzleDatabaseAdapter let isUnique = true; if (memory.embedding) { - elizaLogger.info("Searching for similar memories:"); + logger.info("Searching for similar memories:"); const similarMemories = await this.searchMemoriesByEmbedding( memory.embedding, { @@ -844,7 +844,7 @@ export class DrizzleDatabaseAdapter ) ); - elizaLogger.debug("All memories removed successfully:", { + logger.debug("All memories removed successfully:", { roomId, tableName, }); @@ -931,7 +931,7 @@ export class DrizzleDatabaseAdapter }) .where(eq(goalTable.id, goal.id as string)); } catch (error) { - elizaLogger.error("Failed to update goal:", { + logger.error("Failed to update goal:", { error: error instanceof Error ? error.message : String(error), goalId: goal.id, @@ -953,7 +953,7 @@ export class DrizzleDatabaseAdapter objectives: sql`${goal.objectives}::jsonb`, }); } catch (error) { - elizaLogger.error("Failed to update goal:", { + logger.error("Failed to update goal:", { goalId: goal.id, error: error instanceof Error ? error.message : String(error), status: goal.status, @@ -969,12 +969,12 @@ export class DrizzleDatabaseAdapter try { await this.db.delete(goalTable).where(eq(goalTable.id, goalId)); - elizaLogger.debug("Goal removal attempt:", { + logger.debug("Goal removal attempt:", { goalId, removed: true, }); } catch (error) { - elizaLogger.error("Failed to remove goal:", { + logger.error("Failed to remove goal:", { error: error instanceof Error ? error.message : String(error), goalId, @@ -1055,7 +1055,7 @@ export class DrizzleDatabaseAdapter }); return true; } catch (error) { - elizaLogger.error("Failed to add participant:", { + logger.error("Failed to add participant:", { error: error instanceof Error ? error.message : String(error), userId, @@ -1081,7 +1081,7 @@ export class DrizzleDatabaseAdapter return result.length > 0; } catch (error) { - elizaLogger.error("Failed to remove participant:", { + logger.error("Failed to remove participant:", { error: error instanceof Error ? error.message : String(error), userId, @@ -1183,7 +1183,7 @@ export class DrizzleDatabaseAdapter userId: params.userA, }); - elizaLogger.debug("Relationship created successfully:", { + logger.debug("Relationship created successfully:", { relationshipId, userA: params.userA, userB: params.userB, @@ -1194,7 +1194,7 @@ export class DrizzleDatabaseAdapter // Check for unique constraint violation or other specific errors if ((error as { code?: string }).code === "23505") { // Unique violation - elizaLogger.warn("Relationship already exists:", { + logger.warn("Relationship already exists:", { userA: params.userA, userB: params.userB, error: @@ -1203,7 +1203,7 @@ export class DrizzleDatabaseAdapter : String(error), }); } else { - elizaLogger.error("Failed to create relationship:", { + logger.error("Failed to create relationship:", { userA: params.userA, userB: params.userB, error: @@ -1248,13 +1248,13 @@ export class DrizzleDatabaseAdapter return result[0] as unknown as Relationship; } - elizaLogger.debug("No relationship found between users:", { + logger.debug("No relationship found between users:", { userA: params.userA, userB: params.userB, }); return null; } catch (error) { - elizaLogger.error("Error fetching relationship:", { + logger.error("Error fetching relationship:", { userA: params.userA, userB: params.userB, error: @@ -1282,14 +1282,14 @@ export class DrizzleDatabaseAdapter ) .orderBy(desc(relationshipTable.createdAt)); - elizaLogger.debug("Retrieved relationships:", { + logger.debug("Retrieved relationships:", { userId: params.userId, count: result.length, }); return result as unknown as Relationship[]; } catch (error) { - elizaLogger.error("Failed to fetch relationships:", { + logger.error("Failed to fetch relationships:", { userId: params.userId, error: error instanceof Error ? error.message : String(error), @@ -1345,7 +1345,7 @@ export class DrizzleDatabaseAdapter .delete(knowledgeTable) .where(eq(knowledgeTable.id, id)); } catch (error) { - elizaLogger.error("Failed to remove knowledge:", { + logger.error("Failed to remove knowledge:", { error: error instanceof Error ? error.message : String(error), id, @@ -1392,7 +1392,7 @@ export class DrizzleDatabaseAdapter return result[0]?.value || undefined; } catch (error) { - elizaLogger.error("Error fetching cache", { + logger.error("Error fetching cache", { error: error instanceof Error ? error.message : String(error), key: params.key, @@ -1425,7 +1425,7 @@ export class DrizzleDatabaseAdapter }); return true; } catch (error) { - elizaLogger.error("Error setting cache", { + logger.error("Error setting cache", { error: error instanceof Error ? error.message : String(error), key: params.key, @@ -1452,7 +1452,7 @@ export class DrizzleDatabaseAdapter ); return true; } catch (error) { - elizaLogger.error("Error deleting cache", { + logger.error("Error deleting cache", { error: error instanceof Error ? error.message : String(error), key: params.key, diff --git a/packages/plugin-drizzle/src/migrations.ts b/packages/plugin-drizzle/src/migrations.ts index 6478227582c..148393396bb 100644 --- a/packages/plugin-drizzle/src/migrations.ts +++ b/packages/plugin-drizzle/src/migrations.ts @@ -2,8 +2,8 @@ import { migrate } from "drizzle-orm/node-postgres/migrator"; import { fileURLToPath } from 'url'; import path from "path"; import { drizzle } from "drizzle-orm/node-postgres"; -import { Pool } from "pg"; -import { elizaLogger } from "@elizaos/core"; +import type { Pool } from "pg"; +import { logger } from "@elizaos/core"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -14,9 +14,9 @@ export async function runMigrations(pgPool: Pool): Promise { await migrate(db, { migrationsFolder: path.resolve(__dirname, "../drizzle/migrations"), }); - elizaLogger.info("Migrations completed successfully!"); + logger.info("Migrations completed successfully!"); } catch (error) { - elizaLogger.error("Failed to run database migrations:", error); + logger.error("Failed to run database migrations:", error); throw error; } } diff --git a/packages/plugin-local-ai/biome.json b/packages/plugin-local-ai/biome.json new file mode 100644 index 00000000000..d7e0349301a --- /dev/null +++ b/packages/plugin-local-ai/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-local-ai/echogarden.d.ts b/packages/plugin-local-ai/echogarden.d.ts new file mode 100644 index 00000000000..b678af4e0a3 --- /dev/null +++ b/packages/plugin-local-ai/echogarden.d.ts @@ -0,0 +1,20 @@ +declare module "echogarden" { + interface SynthesizeOptions { + engine: string; + voice: string; + } + + interface RawAudio { + audioChannels: { buffer: ArrayBuffer }[]; + sampleRate: number; + } + + interface SynthesizeResult { + audio: Buffer | RawAudio; + } + + export function synthesize( + text: string, + options: SynthesizeOptions + ): Promise; +} diff --git a/packages/plugin-local-ai/environment.ts b/packages/plugin-local-ai/environment.ts new file mode 100644 index 00000000000..c218e7e1ae6 --- /dev/null +++ b/packages/plugin-local-ai/environment.ts @@ -0,0 +1,36 @@ +import type { IAgentRuntime } from "@elizaos/core"; +import { z } from "zod"; + +export const nodeEnvSchema = z.object({ + VITS_VOICE: z.string().optional(), + VITS_MODEL: z.string().optional(), +}); + +export type NodeConfig = z.infer; + +export async function validateNodeConfig( + runtime: IAgentRuntime +): Promise { + try { + const voiceSettings = runtime.character.settings?.voice; + + // Only include what's absolutely required + const config = { + // VITS settings + VITS_VOICE: voiceSettings?.model || process.env.VITS_VOICE, + VITS_MODEL: process.env.VITS_MODEL, + }; + + return nodeEnvSchema.parse(config); + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessages = error.errors + .map((err) => `${err.path.join(".")}: ${err.message}`) + .join("\n"); + throw new Error( + `Node configuration validation failed:\n${errorMessages}` + ); + } + throw error; + } +} diff --git a/packages/plugin-local-ai/package.json b/packages/plugin-local-ai/package.json index c436e8a70d0..3f1e0c6f3b1 100644 --- a/packages/plugin-local-ai/package.json +++ b/packages/plugin-local-ai/package.json @@ -23,7 +23,59 @@ "@huggingface/transformers": "^3.3.3", "fastembed": "^1.14.1", "tsup": "8.3.5", - "zod": "3.21.4" + "zod": "3.21.4", + "@aws-sdk/client-s3": "^3.705.0", + "@aws-sdk/s3-request-presigner": "^3.705.0", + "@cliqz/adblocker-playwright": "1.34.0", + "@echogarden/espeak-ng-emscripten": "0.3.3", + "@echogarden/kissfft-wasm": "0.2.0", + "@echogarden/speex-resampler-wasm": "0.2.1", + "@opendocsg/pdf2md": "0.1.32", + "@types/uuid": "10.0.0", + "alawmulaw": "6.0.0", + "bignumber.js": "9.1.2", + "capsolver-npm": "2.0.2", + "cldr-segmentation": "2.2.1", + "command-exists": "1.2.9", + "csv-writer": "1.6.0", + "echogarden": "2.0.7", + "espeak-ng": "1.0.2", + "ffmpeg-static": "5.2.0", + "fluent-ffmpeg": "2.1.3", + "formdata-node": "6.0.3", + "fs-extra": "11.2.0", + "gaxios": "6.7.1", + "glob": "11.0.0", + "graceful-fs": "4.2.11", + "html-escaper": "3.0.3", + "html-to-text": "9.0.5", + "import-meta-resolve": "4.1.0", + "jieba-wasm": "2.2.0", + "json5": "2.2.3", + "kuromoji": "0.1.2", + "libsodium-wrappers": "0.7.15", + "multer": "1.4.5-lts.1", + "node-cache": "5.1.2", + "node-llama-cpp": "3.1.1", + "nodejs-whisper": "0.1.18", + "onnxruntime-node": "1.20.1", + "pdfjs-dist": "4.7.76", + "playwright": "1.48.2", + "pm2": "5.4.3", + "puppeteer-extra": "3.3.6", + "puppeteer-extra-plugin-capsolver": "2.0.1", + "sharp": "0.33.5", + "srt": "0.0.3", + "systeminformation": "5.23.8", + "tar": "7.4.3", + "tinyld": "1.3.4", + "uuid": "11.0.3", + "wav": "1.0.2", + "wav-encoder": "1.3.0", + "wavefile": "11.0.0", + "yargs": "17.7.2", + "youtube-dl-exec": "3.0.15", + "cookie": "0.7.0" }, "scripts": { "build": "tsup --format esm --dts", diff --git a/packages/plugin-local-ai/src/index.ts b/packages/plugin-local-ai/src/index.ts index 01938022d35..ae292618386 100644 --- a/packages/plugin-local-ai/src/index.ts +++ b/packages/plugin-local-ai/src/index.ts @@ -1,110 +1,448 @@ -import { ModelClass, Plugin, logger } from "@elizaos/core"; -import { AutoTokenizer } from "@huggingface/transformers"; +import { type IAgentRuntime, logger, ModelClass, type Plugin } from "@elizaos/core"; +import { + AutoProcessor, + AutoTokenizer, + env, + Florence2ForConditionalGeneration, + type Florence2Processor, + type PreTrainedModel, + type PreTrainedTokenizer, + RawImage, + type Tensor, +} from "@huggingface/transformers"; +import { exec } from "child_process"; +import * as Echogarden from "echogarden"; import { EmbeddingModel, FlagEmbedding } from "fastembed"; -import path from "node:path"; +import fs from "fs"; +import { + getLlama, + type Llama, + LlamaChatSession, + type LlamaChatSessionRepeatPenalty, + type LlamaContext, + type LlamaContextSequence, + type LlamaModel +} from "node-llama-cpp"; +import { nodewhisper } from "nodejs-whisper"; +import os from "os"; +import path from "path"; +import { PassThrough, Readable } from "stream"; import { fileURLToPath } from "url"; +import { promisify } from "util"; import { z } from "zod"; -// Configuration schema for the local AI plugin +const execAsync = promisify(exec); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Configuration schema const configSchema = z.object({ LLAMALOCAL_PATH: z.string().optional(), OLLAMA_MODEL: z.string().optional(), TOKENIZER_MODEL: z.string().optional().default("gpt-4o"), CACHE_DIR: z.string().optional().default("./cache"), + VITS_VOICE: z.string().optional(), + VITS_MODEL: z.string().optional(), }); +// Utility functions +function getWavHeader( + audioLength: number, + sampleRate: number, + channelCount = 1, + bitsPerSample = 16 +): Buffer { + const wavHeader = Buffer.alloc(44); + wavHeader.write("RIFF", 0); + wavHeader.writeUInt32LE(36 + audioLength, 4); + wavHeader.write("WAVE", 8); + wavHeader.write("fmt ", 12); + wavHeader.writeUInt32LE(16, 16); + wavHeader.writeUInt16LE(1, 20); + wavHeader.writeUInt16LE(channelCount, 22); + wavHeader.writeUInt32LE(sampleRate, 24); + wavHeader.writeUInt32LE((sampleRate * bitsPerSample * channelCount) / 8, 28); + wavHeader.writeUInt16LE((bitsPerSample * channelCount) / 8, 32); + wavHeader.writeUInt16LE(bitsPerSample, 34); + wavHeader.write("data", 36); + wavHeader.writeUInt32LE(audioLength, 40); + return wavHeader; +} + +function prependWavHeader( + readable: Readable, + audioLength: number, + sampleRate: number, + channelCount = 1, + bitsPerSample = 16 +): Readable { + const wavHeader = getWavHeader(audioLength, sampleRate, channelCount, bitsPerSample); + let pushedHeader = false; + const passThrough = new PassThrough(); + readable.on("data", (data) => { + if (!pushedHeader) { + passThrough.push(wavHeader); + pushedHeader = true; + } + passThrough.push(data); + }); + readable.on("end", () => { + passThrough.end(); + }); + return passThrough; +} + +// Words to punish in LLM responses +const wordsToPunish = [ + " please", " feel", " free", "!", "–", "—", "?", ".", ",", "; ", + " cosmos", " tapestry", " tapestries", " glitch", " matrix", " cyberspace", + " troll", " questions", " topics", " discuss", " basically", " simulation", + " simulate", " universe", " like", " debug", " debugging", " wild", + " existential", " juicy", " circuits", " help", " ask", " happy", " just", + " cosmic", " cool", " joke", " punchline", " fancy", " glad", " assist", + " algorithm", " Indeed", " Furthermore", " However", " Notably", " Therefore" +]; + class LocalAIManager { + private static instance: LocalAIManager | null = null; + private llama: Llama | undefined; + private model: LlamaModel | undefined; + private modelPath: string; + private grammar: any; + private ctx: LlamaContext | undefined; + private sequence: LlamaContextSequence | undefined; tokenizer: any; - embeddingModel: FlagEmbedding | null; + private embeddingModel: FlagEmbedding | null = null; + private embeddingInitPromise: Promise | null = null; + private embeddingInitLock = false; + private florenceModel: PreTrainedModel | null = null; + private florenceProcessor: Florence2Processor | null = null; + private florenceTokenizer: PreTrainedTokenizer | null = null; + private isCudaAvailable = false; + private CONTENT_CACHE_DIR: string; + private TARGET_SAMPLE_RATE = 16000; + + constructor() { + const modelName = "model.gguf"; + this.modelPath = path.join(process.env.LLAMALOCAL_PATH?.trim() ?? "./", modelName); + this.CONTENT_CACHE_DIR = path.join(__dirname, "../../content_cache"); + this.ensureCacheDirectoryExists(); + this.detectCuda(); + } + + private ensureCacheDirectoryExists() { + if (!fs.existsSync(this.CONTENT_CACHE_DIR)) { + fs.mkdirSync(this.CONTENT_CACHE_DIR, { recursive: true }); + } + } + + private async detectCuda() { + const platform = os.platform(); + if (platform === "linux") { + try { + fs.accessSync("/usr/local/cuda/bin/nvcc", fs.constants.X_OK); + this.isCudaAvailable = true; + logger.log("CUDA detected. Acceleration available."); + } catch { + logger.log("CUDA not detected. Using CPU only."); + } + } else if (platform === "win32") { + const cudaPath = process.env.CUDA_PATH || "C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v11.0"; + if (fs.existsSync(path.join(cudaPath, "bin", "nvcc.exe"))) { + this.isCudaAvailable = true; + logger.log("CUDA detected. Acceleration available."); + } + } + } + + async initialize() { + await this.initializeLlama(); + await this.initializeFlorence(); + await this.initializeTokenizer(); + await this.initializeEmbedding(); + } + + private async initializeLlama() { + try { + if (!fs.existsSync(this.modelPath)) { + logger.info("Downloading LLaMA model..."); + // Add model download logic here + } + + this.llama = await getLlama({ + gpu: this.isCudaAvailable ? "cuda" : undefined, + }); + + this.model = await this.llama.loadModel({ + modelPath: this.modelPath, + }); + + this.ctx = await this.model.createContext({ contextSize: 8192 }); + this.sequence = this.ctx.getSequence(); + + logger.success("LLaMA initialization complete"); + } catch (error) { + logger.error("LLaMA initialization failed:", error); + throw error; + } + } + + private async initializeFlorence() { + try { + env.allowLocalModels = false; + env.allowRemoteModels = true; + env.backends.onnx.logLevel = "fatal"; + const modelId = "onnx-community/Florence-2-base-ft"; + + logger.info("Downloading Florence model..."); + this.florenceModel = await Florence2ForConditionalGeneration.from_pretrained(modelId, { + device: "gpu", + progress_callback: (progress) => { + if (progress.status === "download") { + const percent = (((progress as any).loaded / (progress as any).total) * 100).toFixed(1); + const dots = ".".repeat(Math.floor(Number(percent) / 5)); + logger.info(`Downloading Florence model: [${dots.padEnd(20, " ")}] ${percent}%`); + } + }, + }); + + logger.info("Downloading processor..."); + this.florenceProcessor = await AutoProcessor.from_pretrained(modelId, { + device: "gpu", + progress_callback: (progress) => { + if (progress.status === "download") { + const percent = ((progress.loaded / progress.total) * 100).toFixed(1); + const dots = ".".repeat(Math.floor(Number(percent) / 5)); + logger.info(`Downloading Florence model: [${dots.padEnd(20, " ")}] ${percent}%`); + } + }, + }) as Florence2Processor; + + logger.info("Downloading tokenizer..."); + this.florenceTokenizer = await AutoTokenizer.from_pretrained(modelId); + + logger.success("Florence initialization complete"); + } catch (error) { + logger.error("Florence initialization failed:", error); + throw error; + } + } + + private async initializeEmbedding(): Promise { + // If already initialized, return immediately + if (this.embeddingModel) { + return; + } + + // If initialization is in progress, wait for it + if (this.embeddingInitPromise) { + return this.embeddingInitPromise; + } + + // Use a lock to prevent multiple simultaneous initializations + if (this.embeddingInitLock) { + // Wait for current initialization to complete + while (this.embeddingInitLock) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + return; + } - async initializeEmbeddingModel() { - if (this.embeddingModel) return; + this.embeddingInitLock = true; + + try { + this.embeddingInitPromise = this.initializeEmbeddingModel(); + await this.embeddingInitPromise; + } finally { + this.embeddingInitLock = false; + this.embeddingInitPromise = null; + } + } + private async initializeEmbeddingModel(): Promise { try { - // Get cache directory - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - const cacheDir = path.resolve( - __dirname, - process.env.CACHE_DIR || "./cache" - ); - - // Ensure cache directory exists - const fs = await import("fs"); + const cacheDir = path.resolve(__dirname, process.env.CACHE_DIR || "./cache"); if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }); } logger.debug("Initializing BGE embedding model..."); - this.embeddingModel = await FlagEmbedding.init({ cacheDir: cacheDir, model: EmbeddingModel.BGESmallENV15, maxLength: 512, }); + + logger.success("Embedding model initialization complete"); + } catch (error) { + logger.error("Embedding initialization failed:", error); + throw error; + } + } - logger.debug("BGE model initialized successfully"); + // LLaMA text generation + async generateText(context: string, temperature = 0.7, stopSequences: string[] = []): Promise { + if (!this.sequence) { + throw new Error("LLaMA model not initialized"); + } + + const session = new LlamaChatSession({ contextSequence: this.sequence }); + const wordsToPunishTokens = wordsToPunish.flatMap((word) => this.model!.tokenize(word)); + + const repeatPenalty: LlamaChatSessionRepeatPenalty = { + punishTokensFilter: () => wordsToPunishTokens, + penalty: 1.2, + frequencyPenalty: 0.7, + presencePenalty: 0.7, + }; + + const response = await session.prompt(context, { + temperature: temperature, + repeatPenalty: repeatPenalty, + }); + + await this.sequence.clearHistory(); + return response || ""; + } + + private async initializeTokenizer() { + try { + const tokenizerModel = process.env.TOKENIZER_MODEL || "gpt-4o"; + this.tokenizer = await AutoTokenizer.from_pretrained(tokenizerModel); + logger.success(`Tokenizer initialized with model: ${tokenizerModel}`); } catch (error) { - logger.error("Failed to initialize BGE model:", error); + logger.error("Tokenizer initialization failed:", error); throw error; } } + // Embedding generation async generateEmbedding(text: string): Promise { if (!this.embeddingModel) { - await this.initializeEmbeddingModel(); + await this.initializeEmbedding(); + throw new Error("Embedding model not initialized"); } + const embedding = await this.embeddingModel.queryEmbed(text); + return Array.from(embedding); + } + + // Image description + async describeImage(imageData: Buffer, mimeType: string): Promise<{ title: string; description: string }> { + if (!this.florenceModel || !this.florenceProcessor || !this.florenceTokenizer) { + throw new Error("Florence model not initialized"); + } + + const blob = new Blob([imageData], { type: mimeType }); + const image = await RawImage.fromBlob(blob); + const visionInputs = await this.florenceProcessor(image); + const prompts = this.florenceProcessor.construct_prompts(""); + const textInputs = this.florenceTokenizer(prompts); + + const generatedIds = await this.florenceModel.generate({ + ...textInputs, + ...visionInputs, + max_new_tokens: 256, + }) as Tensor; + + const generatedText = this.florenceTokenizer.batch_decode(generatedIds, { + skip_special_tokens: false, + })[0]; + + const result = this.florenceProcessor.post_process_generation( + generatedText, + "", + image.size + ); + + const detailedCaption = result[""] as string; + return { title: detailedCaption, description: detailedCaption }; + } + + // Audio transcription + async transcribeAudio(audioBuffer: ArrayBuffer): Promise { try { - const embedding = await this.embeddingModel!.queryEmbed(text); - return this.processEmbedding(embedding); + if (audioBuffer.byteLength < 0.2 * 16000) { + return null; + } + + const arrayBuffer = new Uint8Array(audioBuffer).buffer; + const tempWavFile = path.join(this.CONTENT_CACHE_DIR, `temp_${Date.now()}.wav`); + fs.writeFileSync(tempWavFile, Buffer.from(arrayBuffer)); + + const output = await nodewhisper(tempWavFile, { + modelName: "base.en", + autoDownloadModelName: "base.en", + verbose: false, + withCuda: this.isCudaAvailable, + whisperOptions: { + outputInText: true, + translateToEnglish: false, + }, + }); + + fs.unlinkSync(tempWavFile); + + if (!output || output.length < 5) { + return null; + } + + return output.split("\n") + .map(line => line.trim().startsWith("[") ? line.substring(line.indexOf("]") + 1) : line) + .join("\n"); } catch (error) { - logger.error("Embedding generation failed:", error); - throw error; + logger.error("Transcription error:", error); + return null; } } - processEmbedding(embedding: number[]): number[] { - let finalEmbedding: number[]; + // Text to speech + async generateSpeech(runtime: IAgentRuntime, text: string): Promise { + try { + const voiceSettings = runtime.character.settings?.voice; + + const vitsVoice = voiceSettings?.model || process.env.VITS_VOICE || "en_US-hfc_female-medium"; + const { audio } = await Echogarden.synthesize(text, { + engine: "vits", + voice: vitsVoice, + }); - if ( - ArrayBuffer.isView(embedding) && - embedding.constructor === Float32Array - ) { - finalEmbedding = Array.from(embedding); - } else if ( - Array.isArray(embedding) && - ArrayBuffer.isView(embedding[0]) && - embedding[0].constructor === Float32Array - ) { - finalEmbedding = Array.from(embedding[0]); - } else if (Array.isArray(embedding)) { - finalEmbedding = embedding; - } else { - throw new Error(`Unexpected embedding format: ${typeof embedding}`); + return this.processVitsAudio(audio); + } catch (error) { + logger.error("Speech generation error:", error); + throw error; } + } - finalEmbedding = finalEmbedding.map((n) => Number(n)); - - if (!Array.isArray(finalEmbedding) || finalEmbedding[0] === undefined) { - throw new Error( - "Invalid embedding format: must be an array starting with a number" - ); + private async processVitsAudio(audio: any): Promise { + if (audio instanceof Buffer) { + return Readable.from(audio); } - if (finalEmbedding.length !== 384) { - logger.warn(`Unexpected embedding dimension: ${finalEmbedding.length}`); + if ("audioChannels" in audio && "sampleRate" in audio) { + const floatBuffer = Buffer.from(audio.audioChannels[0].buffer); + const floatArray = new Float32Array(floatBuffer.buffer); + const pcmBuffer = new Int16Array(floatArray.length); + + for (let i = 0; i < floatArray.length; i++) { + pcmBuffer[i] = Math.round(floatArray[i] * 32767); + } + + const wavHeaderBuffer = getWavHeader(pcmBuffer.length * 2, audio.sampleRate, 1, 16); + const wavBuffer = Buffer.concat([wavHeaderBuffer, Buffer.from(pcmBuffer.buffer)]); + return Readable.from(wavBuffer); } - return finalEmbedding; + throw new Error("Unsupported audio format"); } } +// Create manager instance const localAIManager = new LocalAIManager(); export const localAIPlugin: Plugin = { name: "local-ai", - description: "Local AI plugin using LLaMA, AutoTokenizer and FastEmbed", + description: "Local AI plugin using LLaMA, Florence, and other local models", async init(config: Record) { try { @@ -113,21 +451,11 @@ export const localAIPlugin: Plugin = { if (value) process.env[key] = value; }); - // Initialize tokenizer - const tokenizerModel = validatedConfig.TOKENIZER_MODEL; - localAIManager.tokenizer = await AutoTokenizer.from_pretrained( - tokenizerModel - ); - logger.info(`Initialized AutoTokenizer with model: ${tokenizerModel}`); - - // Pre-initialize embedding model - await localAIManager.initializeEmbeddingModel(); + await localAIManager.initialize(); } catch (error) { if (error instanceof z.ZodError) { throw new Error( - `Invalid plugin configuration: ${error.errors - .map((e) => e.message) - .join(", ")}` + `Invalid plugin configuration: ${error.errors.map((e) => e.message).join(", ")}` ); } throw error; @@ -135,49 +463,25 @@ export const localAIPlugin: Plugin = { }, models: { - // Text generation for small tasks - [ModelClass.TEXT_SMALL]: async ( - runtime, - { - context, - stopSequences = [], - }) => { + [ModelClass.TEXT_SMALL]: async (runtime, { context, stopSequences = [], temperature = 0.7 }) => { try { - const modelPath = process.env.LLAMALOCAL_PATH || "./model.gguf"; - - // TODO: implement hermes here - - - + return await localAIManager.generateText(context, temperature, stopSequences); } catch (error) { logger.error("Error in TEXT_SMALL handler:", error); throw error; } }, - // Text generation for larger tasks - [ModelClass.TEXT_LARGE]: async ( - runtime, - { - context, - stopSequences = [], - }) => { + [ModelClass.TEXT_LARGE]: async (runtime, { context, stopSequences = [], temperature = 0.7 }) => { try { - const modelPath = process.env.LLAMALOCAL_PATH || "./model.gguf"; - - // TODO: implement hermes - + return await localAIManager.generateText(context, temperature, stopSequences); } catch (error) { logger.error("Error in TEXT_LARGE handler:", error); throw error; } }, - // Text embedding using FastEmbed - [ModelClass.TEXT_EMBEDDING]: async ( - runtime, - { text } - ) => { + [ModelClass.TEXT_EMBEDDING]: async (runtime, text) => { try { return await localAIManager.generateEmbedding(text); } catch (error) { @@ -186,15 +490,8 @@ export const localAIPlugin: Plugin = { } }, - // Text tokenization using AutoTokenizer - [ModelClass.TEXT_TOKENIZER_ENCODE]: async ( - runtime, - { text } - ) => { + [ModelClass.TEXT_TOKENIZER_ENCODE]: async (runtime, { text }) => { try { - if (!localAIManager.tokenizer) { - throw new Error("Tokenizer not initialized"); - } return await localAIManager.tokenizer.encode(text); } catch (error) { logger.error("Error in TEXT_TOKENIZER_ENCODE handler:", error); @@ -202,15 +499,8 @@ export const localAIPlugin: Plugin = { } }, - // Text detokenization using AutoTokenizer - [ModelClass.TEXT_TOKENIZER_DECODE]: async ( - runtime, - { tokens } - ) => { + [ModelClass.TEXT_TOKENIZER_DECODE]: async (runtime, { tokens }) => { try { - if (!localAIManager.tokenizer) { - throw new Error("Tokenizer not initialized"); - } return await localAIManager.tokenizer.decode(tokens); } catch (error) { logger.error("Error in TEXT_TOKENIZER_DECODE handler:", error); @@ -218,19 +508,39 @@ export const localAIPlugin: Plugin = { } }, - // Image description using local Florence model [ModelClass.IMAGE_DESCRIPTION]: async (runtime, imageUrl) => { try { - - // TODO: Add florence - - + const response = await fetch(imageUrl); + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.statusText}`); + } + const imageBuffer = Buffer.from(await response.arrayBuffer()); + const mimeType = response.headers.get("content-type") || "image/jpeg"; + return await localAIManager.describeImage(imageBuffer, mimeType); } catch (error) { logger.error("Error in IMAGE_DESCRIPTION handler:", error); throw error; } }, - }, + + [ModelClass.TRANSCRIPTION]: async (runtime, audioBuffer) => { + try { + return await localAIManager.transcribeAudio(audioBuffer); + } catch (error) { + logger.error("Error in TRANSCRIPTION handler:", error); + throw error; + } + }, + + [ModelClass.TEXT_TO_SPEECH]: async (runtime, text) => { + try { + return await localAIManager.generateSpeech(runtime, text); + } catch (error) { + logger.error("Error in SPEECH_GENERATION handler:", error); + throw error; + } + } + } }; -export default localAIPlugin; +export default localAIPlugin; \ No newline at end of file diff --git a/packages/plugin-node/biome.json b/packages/plugin-node/biome.json new file mode 100644 index 00000000000..d7e0349301a --- /dev/null +++ b/packages/plugin-node/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-node/package.json b/packages/plugin-node/package.json index eb1137912bb..aaac96d5a18 100644 --- a/packages/plugin-node/package.json +++ b/packages/plugin-node/package.json @@ -30,7 +30,6 @@ "@echogarden/kissfft-wasm": "0.2.0", "@echogarden/speex-resampler-wasm": "0.2.1", "@elizaos/core": "workspace:*", - "@huggingface/transformers": "3.0.2", "@opendocsg/pdf2md": "0.1.32", "@types/uuid": "10.0.0", "alawmulaw": "6.0.0", diff --git a/packages/plugin-openai/biome.json b/packages/plugin-openai/biome.json new file mode 100644 index 00000000000..d7e0349301a --- /dev/null +++ b/packages/plugin-openai/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-openai/src/index.ts b/packages/plugin-openai/src/index.ts index c0a4df07d5c..1f8a2d826dc 100644 --- a/packages/plugin-openai/src/index.ts +++ b/packages/plugin-openai/src/index.ts @@ -1,10 +1,10 @@ import { createOpenAI } from "@ai-sdk/openai"; import type { IAgentRuntime, Plugin } from "@elizaos/core"; import { - DetokenizeTextParams, - GenerateTextParams, + type DetokenizeTextParams, + type GenerateTextParams, ModelClass, - TokenizeTextParams, + type TokenizeTextParams, } from "@elizaos/core"; import { generateText as aiGenerateText } from "ai"; import { encodingForModel, type TiktokenModel } from "js-tiktoken"; @@ -246,7 +246,7 @@ export const openaiPlugin: Plugin = { { role: "user", content: [{ - type: "image" as "image", + type: "image" as const, image: imageUrl }] } diff --git a/packages/plugin-sqlite/biome.json b/packages/plugin-sqlite/biome.json new file mode 100644 index 00000000000..d7e0349301a --- /dev/null +++ b/packages/plugin-sqlite/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-tee/biome.json b/packages/plugin-tee/biome.json index 818716a6219..d7e0349301a 100644 --- a/packages/plugin-tee/biome.json +++ b/packages/plugin-tee/biome.json @@ -1,24 +1,9 @@ { "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], "organizeImports": { "enabled": false }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "correctness": { - "noUnusedVariables": "error" - }, - "suspicious": { - "noExplicitAny": "error" - }, - "style": { - "useConst": "error", - "useImportType": "off" - } - } - }, "formatter": { "enabled": true, "indentStyle": "space", @@ -27,8 +12,7 @@ }, "javascript": { "formatter": { - "quoteStyle": "single", - "trailingCommas": "es5" + "quoteStyle": "single" } }, "files": { diff --git a/packages/plugin-tee/src/__tests__/deriveKey.test.ts b/packages/plugin-tee/src/__tests__/deriveKey.test.ts index deabae7bcaf..318989e1b30 100644 --- a/packages/plugin-tee/src/__tests__/deriveKey.test.ts +++ b/packages/plugin-tee/src/__tests__/deriveKey.test.ts @@ -7,30 +7,30 @@ import { TEEMode } from '../src/types/tee'; vi.mock('@phala/dstack-sdk', () => ({ TappdClient: vi.fn().mockImplementation(() => ({ deriveKey: vi.fn().mockResolvedValue({ - asUint8Array: () => new Uint8Array([1, 2, 3, 4, 5]) + asUint8Array: () => new Uint8Array([1, 2, 3, 4, 5]), }), tdxQuote: vi.fn().mockResolvedValue({ quote: 'mock-quote-data', - replayRtmrs: () => ['rtmr0', 'rtmr1', 'rtmr2', 'rtmr3'] + replayRtmrs: () => ['rtmr0', 'rtmr1', 'rtmr2', 'rtmr3'], }), - rawDeriveKey: vi.fn() - })) + rawDeriveKey: vi.fn(), + })), })); vi.mock('@solana/web3.js', () => ({ Keypair: { fromSeed: vi.fn().mockReturnValue({ publicKey: { - toBase58: () => 'mock-solana-public-key' - } - }) - } + toBase58: () => 'mock-solana-public-key', + }, + }), + }, })); vi.mock('viem/accounts', () => ({ privateKeyToAccount: vi.fn().mockReturnValue({ - address: 'mock-evm-address' - }) + address: 'mock-evm-address', + }), })); describe('DeriveKeyProvider', () => { @@ -108,4 +108,4 @@ describe('DeriveKeyProvider', () => { expect(result.keypair.publicKey.toBase58()).toEqual('mock-solana-public-key'); }); }); -}); \ No newline at end of file +}); diff --git a/packages/plugin-tee/src/__tests__/remoteAttestation.test.ts b/packages/plugin-tee/src/__tests__/remoteAttestation.test.ts index 4890d6683e5..85d078d5ac3 100644 --- a/packages/plugin-tee/src/__tests__/remoteAttestation.test.ts +++ b/packages/plugin-tee/src/__tests__/remoteAttestation.test.ts @@ -8,10 +8,10 @@ vi.mock('@phala/dstack-sdk', () => ({ TappdClient: vi.fn().mockImplementation(() => ({ tdxQuote: vi.fn().mockResolvedValue({ quote: 'mock-quote-data', - replayRtmrs: () => ['rtmr0', 'rtmr1', 'rtmr2', 'rtmr3'] + replayRtmrs: () => ['rtmr0', 'rtmr1', 'rtmr2', 'rtmr3'], }), - deriveKey: vi.fn() - })) + deriveKey: vi.fn(), + })), })); describe('RemoteAttestationProvider', () => { @@ -53,7 +53,7 @@ describe('RemoteAttestationProvider', () => { expect(quote).toEqual({ quote: 'mock-quote-data', - timestamp: expect.any(Number) + timestamp: expect.any(Number), }); }); @@ -62,11 +62,13 @@ describe('RemoteAttestationProvider', () => { const mockTdxQuote = vi.fn().mockRejectedValue(mockError); vi.mocked(TappdClient).mockImplementationOnce(() => ({ tdxQuote: mockTdxQuote, - deriveKey: vi.fn() + deriveKey: vi.fn(), })); const provider = new RemoteAttestationProvider(TEEMode.LOCAL); - await expect(provider.generateAttestation('test-data')).rejects.toThrow('Failed to generate TDX Quote'); + await expect(provider.generateAttestation('test-data')).rejects.toThrow( + 'Failed to generate TDX Quote', + ); }); it('should pass hash algorithm to tdxQuote when provided', async () => { @@ -78,4 +80,4 @@ describe('RemoteAttestationProvider', () => { expect(client.tdxQuote).toHaveBeenCalledWith(reportData, hashAlgorithm); }); }); -}); \ No newline at end of file +}); diff --git a/packages/plugin-tee/src/__tests__/remoteAttestationAction.test.ts b/packages/plugin-tee/src/__tests__/remoteAttestationAction.test.ts index 897de2c7d86..5ae17f61b9c 100644 --- a/packages/plugin-tee/src/__tests__/remoteAttestationAction.test.ts +++ b/packages/plugin-tee/src/__tests__/remoteAttestationAction.test.ts @@ -6,8 +6,8 @@ import { RemoteAttestationProvider } from '../src/providers/remoteAttestationPro vi.mock('../src/providers/remoteAttestationProvider'); vi.mock('undici', () => ({ fetch: vi.fn().mockResolvedValue({ - json: () => Promise.resolve({ checksum: 'mock-checksum' }) - }) + json: () => Promise.resolve({ checksum: 'mock-checksum' }), + }), })); describe('remoteAttestationAction', () => { @@ -16,8 +16,12 @@ describe('remoteAttestationAction', () => { getSetting: vi.fn().mockReturnValue('LOCAL'), getState: vi.fn(), setState: vi.fn(), - message:{ userId: 'user', roomId: 'room', content: { text: 'If you are running in a TEE, generate a remote attestation' } }, - setConversation: vi.fn() + message: { + userId: 'user', + roomId: 'room', + content: { text: 'If you are running in a TEE, generate a remote attestation' }, + }, + setConversation: vi.fn(), }; const mockCallback = vi.fn(); @@ -27,8 +31,8 @@ describe('remoteAttestationAction', () => { vi.mocked(RemoteAttestationProvider).mockImplementation(() => ({ generateAttestation: vi.fn().mockResolvedValue({ quote: '0x1234', - timestamp: Date.now() - }) + timestamp: Date.now(), + }), })); }); @@ -46,13 +50,13 @@ describe('remoteAttestationAction', () => { mockRuntime.message, {}, {}, - mockCallback + mockCallback, ); expect(result).toBe(true); expect(mockCallback).toHaveBeenCalledWith({ text: expect.stringContaining('https://proof.t16z.com/reports/mock-checksum'), - action: 'NONE' + action: 'NONE', }); }); @@ -62,8 +66,8 @@ describe('remoteAttestationAction', () => { generateAttestation: vi.fn().mockRejectedValueOnce(mockError), client: { tdxQuote: vi.fn(), - deriveKey: vi.fn() - } + deriveKey: vi.fn(), + }, })); const result = await remoteAttestationAction.handler( @@ -71,7 +75,7 @@ describe('remoteAttestationAction', () => { {}, {}, {}, - mockCallback + mockCallback, ); expect(result).toBe(false); @@ -92,7 +96,9 @@ describe('remoteAttestationAction', () => { const [userMessage, agentMessage] = remoteAttestationAction.examples[0]; expect(userMessage.user).toBe('{{user1}}'); - expect(userMessage.content.text).toBe('If you are running in a TEE, generate a remote attestation'); + expect(userMessage.content.text).toBe( + 'If you are running in a TEE, generate a remote attestation', + ); expect(userMessage.content.action).toBe('REMOTE_ATTESTATION'); expect(agentMessage.user).toBe('{{user2}}'); @@ -100,4 +106,4 @@ describe('remoteAttestationAction', () => { expect(agentMessage.content.action).toBeUndefined(); }); }); -}); \ No newline at end of file +}); diff --git a/packages/plugin-tee/src/__tests__/timeout.test.ts b/packages/plugin-tee/src/__tests__/timeout.test.ts index f16a54d8a49..3c4906c079b 100644 --- a/packages/plugin-tee/src/__tests__/timeout.test.ts +++ b/packages/plugin-tee/src/__tests__/timeout.test.ts @@ -8,8 +8,8 @@ import { TappdClient } from '@phala/dstack-sdk'; vi.mock('@phala/dstack-sdk', () => ({ TappdClient: vi.fn().mockImplementation(() => ({ tdxQuote: vi.fn(), - deriveKey: vi.fn() - })) + deriveKey: vi.fn(), + })), })); describe('TEE Provider Timeout Tests', () => { @@ -19,18 +19,17 @@ describe('TEE Provider Timeout Tests', () => { describe('RemoteAttestationProvider', () => { it('should handle API timeout during attestation generation', async () => { - const mockTdxQuote = vi.fn() - .mockRejectedValueOnce(new Error('Request timed out')); + const mockTdxQuote = vi.fn().mockRejectedValueOnce(new Error('Request timed out')); vi.mocked(TappdClient).mockImplementation(() => ({ tdxQuote: mockTdxQuote, - deriveKey: vi.fn() + deriveKey: vi.fn(), })); const provider = new RemoteAttestationProvider(TEEMode.LOCAL); - await expect(() => provider.generateAttestation('test-data')) - .rejects - .toThrow('Failed to generate TDX Quote: Request timed out'); + await expect(() => provider.generateAttestation('test-data')).rejects.toThrow( + 'Failed to generate TDX Quote: Request timed out', + ); // Verify the call was made once expect(mockTdxQuote).toHaveBeenCalledTimes(1); @@ -38,18 +37,17 @@ describe('TEE Provider Timeout Tests', () => { }); it('should handle network errors during attestation generation', async () => { - const mockTdxQuote = vi.fn() - .mockRejectedValueOnce(new Error('Network error')); + const mockTdxQuote = vi.fn().mockRejectedValueOnce(new Error('Network error')); vi.mocked(TappdClient).mockImplementation(() => ({ tdxQuote: mockTdxQuote, - deriveKey: vi.fn() + deriveKey: vi.fn(), })); const provider = new RemoteAttestationProvider(TEEMode.LOCAL); - await expect(() => provider.generateAttestation('test-data')) - .rejects - .toThrow('Failed to generate TDX Quote: Network error'); + await expect(() => provider.generateAttestation('test-data')).rejects.toThrow( + 'Failed to generate TDX Quote: Network error', + ); expect(mockTdxQuote).toHaveBeenCalledTimes(1); }); @@ -57,14 +55,14 @@ describe('TEE Provider Timeout Tests', () => { it('should handle successful attestation generation', async () => { const mockQuote = { quote: 'test-quote', - replayRtmrs: () => ['rtmr0', 'rtmr1', 'rtmr2', 'rtmr3'] + replayRtmrs: () => ['rtmr0', 'rtmr1', 'rtmr2', 'rtmr3'], }; const mockTdxQuote = vi.fn().mockResolvedValueOnce(mockQuote); vi.mocked(TappdClient).mockImplementation(() => ({ tdxQuote: mockTdxQuote, - deriveKey: vi.fn() + deriveKey: vi.fn(), })); const provider = new RemoteAttestationProvider(TEEMode.LOCAL); @@ -73,43 +71,41 @@ describe('TEE Provider Timeout Tests', () => { expect(mockTdxQuote).toHaveBeenCalledTimes(1); expect(result).toEqual({ quote: 'test-quote', - timestamp: expect.any(Number) + timestamp: expect.any(Number), }); }); }); describe('DeriveKeyProvider', () => { it('should handle API timeout during key derivation', async () => { - const mockDeriveKey = vi.fn() - .mockRejectedValueOnce(new Error('Request timed out')); + const mockDeriveKey = vi.fn().mockRejectedValueOnce(new Error('Request timed out')); vi.mocked(TappdClient).mockImplementation(() => ({ tdxQuote: vi.fn(), - deriveKey: mockDeriveKey + deriveKey: mockDeriveKey, })); const provider = new DeriveKeyProvider(TEEMode.LOCAL); - await expect(() => provider.rawDeriveKey('test-path', 'test-subject')) - .rejects - .toThrow('Request timed out'); + await expect(() => provider.rawDeriveKey('test-path', 'test-subject')).rejects.toThrow( + 'Request timed out', + ); expect(mockDeriveKey).toHaveBeenCalledTimes(1); expect(mockDeriveKey).toHaveBeenCalledWith('test-path', 'test-subject'); }); it('should handle API timeout during Ed25519 key derivation', async () => { - const mockDeriveKey = vi.fn() - .mockRejectedValueOnce(new Error('Request timed out')); + const mockDeriveKey = vi.fn().mockRejectedValueOnce(new Error('Request timed out')); vi.mocked(TappdClient).mockImplementation(() => ({ tdxQuote: vi.fn(), - deriveKey: mockDeriveKey + deriveKey: mockDeriveKey, })); const provider = new DeriveKeyProvider(TEEMode.LOCAL); - await expect(() => provider.deriveEd25519Keypair('test-path', 'test-subject')) - .rejects - .toThrow('Request timed out'); + await expect(() => + provider.deriveEd25519Keypair('test-path', 'test-subject'), + ).rejects.toThrow('Request timed out'); expect(mockDeriveKey).toHaveBeenCalledTimes(1); }); diff --git a/packages/plugin-tee/src/adapters/sqliteDAO.ts b/packages/plugin-tee/src/adapters/sqliteDAO.ts index 9d985c23c8b..fa9d597b597 100644 --- a/packages/plugin-tee/src/adapters/sqliteDAO.ts +++ b/packages/plugin-tee/src/adapters/sqliteDAO.ts @@ -1,6 +1,12 @@ -import type { Database } from "better-sqlite3"; -import { TeeLogDAO, type TeeAgent, type TeeLog, type TeeLogQuery, type TeePageQuery } from "../types.ts"; -import { sqliteTables } from "./sqliteTables.ts"; +import type { Database } from 'better-sqlite3'; +import { + TeeLogDAO, + type TeeAgent, + type TeeLog, + type TeeLogQuery, + type TeePageQuery, +} from '../types.ts'; +import { sqliteTables } from './sqliteTables.ts'; export class SqliteTeeLogDAO extends TeeLogDAO { constructor(db: Database) { @@ -14,18 +20,31 @@ export class SqliteTeeLogDAO extends TeeLogDAO { async addLog(log: TeeLog): Promise { const stmt = this.db.prepare( - "INSERT INTO tee_logs (id, agentId, roomId, userId, type, content, timestamp, signature) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" + 'INSERT INTO tee_logs (id, agentId, roomId, userId, type, content, timestamp, signature) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', ); try { - stmt.run(log.id, log.agentId, log.roomId, log.userId, log.type, log.content, log.timestamp, log.signature); + stmt.run( + log.id, + log.agentId, + log.roomId, + log.userId, + log.type, + log.content, + log.timestamp, + log.signature, + ); return true; } catch (error) { - console.error("Error adding log to database", error); + console.error('Error adding log to database', error); return false; } } - async getPagedLogs(query: TeeLogQuery, page: number, pageSize: number): Promise> { + async getPagedLogs( + query: TeeLogQuery, + page: number, + pageSize: number, + ): Promise> { if (page < 1) { page = 1; } @@ -35,46 +54,46 @@ export class SqliteTeeLogDAO extends TeeLogDAO { const whereConditions = []; const params = []; - if (query.agentId && query.agentId !== "") { - whereConditions.push("agentId = ?"); + if (query.agentId && query.agentId !== '') { + whereConditions.push('agentId = ?'); params.push(query.agentId); } - if (query.roomId && query.roomId !== "") { - whereConditions.push("roomId = ?"); + if (query.roomId && query.roomId !== '') { + whereConditions.push('roomId = ?'); params.push(query.roomId); } - if (query.userId && query.userId !== "") { - whereConditions.push("userId = ?"); + if (query.userId && query.userId !== '') { + whereConditions.push('userId = ?'); params.push(query.userId); } - if (query.type && query.type !== "") { - whereConditions.push("type = ?"); + if (query.type && query.type !== '') { + whereConditions.push('type = ?'); params.push(query.type); } - if (query.containsContent && query.containsContent !== "") { - whereConditions.push("content LIKE ?"); + if (query.containsContent && query.containsContent !== '') { + whereConditions.push('content LIKE ?'); params.push(`%${query.containsContent}%`); } if (query.startTimestamp) { - whereConditions.push("timestamp >= ?"); + whereConditions.push('timestamp >= ?'); params.push(query.startTimestamp); } if (query.endTimestamp) { - whereConditions.push("timestamp <= ?"); + whereConditions.push('timestamp <= ?'); params.push(query.endTimestamp); } const whereClause = - whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : ""; + whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : ''; try { const total_stmt = this.db.prepare( - `SELECT COUNT(*) as total FROM tee_logs ${whereClause}` + `SELECT COUNT(*) as total FROM tee_logs ${whereClause}`, ); const total = total_stmt.get(params).total; const logs_stmt = this.db.prepare( - `SELECT * FROM tee_logs ${whereClause} ORDER BY timestamp ASC LIMIT ? OFFSET ?` + `SELECT * FROM tee_logs ${whereClause} ORDER BY timestamp ASC LIMIT ? OFFSET ?`, ); const logs = logs_stmt.all(...params, limit, offset); @@ -85,40 +104,49 @@ export class SqliteTeeLogDAO extends TeeLogDAO { data: logs, }; } catch (error) { - console.error("Error getting paged logs from database", error); + console.error('Error getting paged logs from database', error); throw error; } } async addAgent(agent: TeeAgent): Promise { const stmt = this.db.prepare( - "INSERT INTO tee_agents (id, agentId, agentName, createdAt, publicKey, attestation) VALUES (?, ?, ?, ?, ?, ?)" + 'INSERT INTO tee_agents (id, agentId, agentName, createdAt, publicKey, attestation) VALUES (?, ?, ?, ?, ?, ?)', ); try { - stmt.run(agent.id, agent.agentId, agent.agentName, agent.createdAt, agent.publicKey, agent.attestation); + stmt.run( + agent.id, + agent.agentId, + agent.agentName, + agent.createdAt, + agent.publicKey, + agent.attestation, + ); return true; } catch (error) { - console.error("Error adding agent to database", error); + console.error('Error adding agent to database', error); return false; } } async getAgent(agentId: string): Promise { - const stmt = this.db.prepare("SELECT * FROM tee_agents WHERE agentId = ? ORDER BY createdAt DESC LIMIT 1"); + const stmt = this.db.prepare( + 'SELECT * FROM tee_agents WHERE agentId = ? ORDER BY createdAt DESC LIMIT 1', + ); try { return stmt.get(agentId); } catch (error) { - console.error("Error getting agent from database", error); + console.error('Error getting agent from database', error); throw error; } } async getAllAgents(): Promise { - const stmt = this.db.prepare("SELECT * FROM tee_agents"); + const stmt = this.db.prepare('SELECT * FROM tee_agents'); try { return stmt.all(); } catch (error) { - console.error("Error getting all agents from database", error); + console.error('Error getting all agents from database', error); throw error; } } diff --git a/packages/plugin-tee/src/index.ts b/packages/plugin-tee/src/index.ts index 7d2ba0a4af8..494b64138dc 100644 --- a/packages/plugin-tee/src/index.ts +++ b/packages/plugin-tee/src/index.ts @@ -1,34 +1,19 @@ -import type { Plugin } from "@elizaos/core"; -import { - deriveKeyProvider, -} from "./providers/deriveKeyProvider"; -import { - remoteAttestationProvider -} from "./providers/remoteAttestationProvider"; +import type { Plugin } from '@elizaos/core'; +import { deriveKeyProvider } from './providers/deriveKeyProvider'; +import { remoteAttestationProvider } from './providers/remoteAttestationProvider'; -export { - DeriveKeyProvider -} from "./providers/deriveKeyProvider"; +export { DeriveKeyProvider } from './providers/deriveKeyProvider'; -import { sgxAttestationProvider } from "./providers/sgxAttestationProvider"; -import { TeeLogService } from "./services/teeLogService"; +import { sgxAttestationProvider } from './providers/sgxAttestationProvider'; +import { TeeLogService } from './services/teeLogService'; export { TeeLogService }; export const teePlugin: Plugin = { - name: "tee", - description: - "TEE plugin with actions to generate remote attestations and derive keys", - actions: [ - ], - evaluators: [ - ], - providers: [ - remoteAttestationProvider, - deriveKeyProvider, - sgxAttestationProvider, - ], - services: [ - new TeeLogService() - ], -}; \ No newline at end of file + name: 'tee', + description: 'TEE plugin with actions to generate remote attestations and derive keys', + actions: [], + evaluators: [], + providers: [remoteAttestationProvider, deriveKeyProvider, sgxAttestationProvider], + services: [new TeeLogService()], +}; diff --git a/packages/plugin-tee/src/providers/deriveKeyProvider.ts b/packages/plugin-tee/src/providers/deriveKeyProvider.ts index 2e41b14010a..70caee42fd3 100644 --- a/packages/plugin-tee/src/providers/deriveKeyProvider.ts +++ b/packages/plugin-tee/src/providers/deriveKeyProvider.ts @@ -1,17 +1,11 @@ -import { - type IAgentRuntime, - type Memory, - type Provider, - type State, - elizaLogger, -} from "@elizaos/core"; -import { Keypair } from "@solana/web3.js"; -import crypto from "crypto"; -import { type DeriveKeyResponse, TappdClient } from "@phala/dstack-sdk"; -import { privateKeyToAccount } from "viem/accounts"; -import { type PrivateKeyAccount, keccak256 } from "viem"; -import { RemoteAttestationProvider } from "./remoteAttestationProvider"; -import { TEEMode, type RemoteAttestationQuote, type DeriveKeyAttestationData } from "@elizaos/core"; +import { type IAgentRuntime, type Memory, type Provider, type State, logger } from '@elizaos/core'; +import { Keypair } from '@solana/web3.js'; +import crypto from 'crypto'; +import { type DeriveKeyResponse, TappdClient } from '@phala/dstack-sdk'; +import { privateKeyToAccount } from 'viem/accounts'; +import { type PrivateKeyAccount, keccak256 } from 'viem'; +import { RemoteAttestationProvider } from './remoteAttestationProvider'; +import { TEEMode, type RemoteAttestationQuote, type DeriveKeyAttestationData } from '@elizaos/core'; class DeriveKeyProvider { private client: TappdClient; @@ -23,26 +17,20 @@ class DeriveKeyProvider { // Both LOCAL and DOCKER modes use the simulator, just with different endpoints switch (teeMode) { case TEEMode.LOCAL: - endpoint = "http://localhost:8090"; - elizaLogger.log( - "TEE: Connecting to local simulator at localhost:8090" - ); + endpoint = 'http://localhost:8090'; + logger.log('TEE: Connecting to local simulator at localhost:8090'); break; case TEEMode.DOCKER: - endpoint = "http://host.docker.internal:8090"; - elizaLogger.log( - "TEE: Connecting to simulator via Docker at host.docker.internal:8090" - ); + endpoint = 'http://host.docker.internal:8090'; + logger.log('TEE: Connecting to simulator via Docker at host.docker.internal:8090'); break; case TEEMode.PRODUCTION: endpoint = undefined; - elizaLogger.log( - "TEE: Running in production mode without simulator" - ); + logger.log('TEE: Running in production mode without simulator'); break; default: throw new Error( - `Invalid TEE_MODE: ${teeMode}. Must be one of: LOCAL, DOCKER, PRODUCTION` + `Invalid TEE_MODE: ${teeMode}. Must be one of: LOCAL, DOCKER, PRODUCTION`, ); } @@ -53,7 +41,7 @@ class DeriveKeyProvider { private async generateDeriveKeyAttestation( agentId: string, publicKey: string, - subject?: string + subject?: string, ): Promise { const deriveKeyData: DeriveKeyAttestationData = { agentId, @@ -61,11 +49,9 @@ class DeriveKeyProvider { subject, }; const reportdata = JSON.stringify(deriveKeyData); - elizaLogger.log( - "Generating Remote Attestation Quote for Derive Key..." - ); + logger.log('Generating Remote Attestation Quote for Derive Key...'); const quote = await this.raProvider.generateAttestation(reportdata); - elizaLogger.log("Remote Attestation Quote generated successfully!"); + logger.log('Remote Attestation Quote generated successfully!'); return quote; } @@ -75,24 +61,19 @@ class DeriveKeyProvider { * @param subject - The subject to derive the key from. This is used for the certificate chain. * @returns The derived key. */ - async rawDeriveKey( - path: string, - subject: string - ): Promise { + async rawDeriveKey(path: string, subject: string): Promise { try { if (!path || !subject) { - elizaLogger.error( - "Path and Subject are required for key derivation" - ); + logger.error('Path and Subject are required for key derivation'); } - elizaLogger.log("Deriving Raw Key in TEE..."); + logger.log('Deriving Raw Key in TEE...'); const derivedKey = await this.client.deriveKey(path, subject); - elizaLogger.log("Raw Key Derived Successfully!"); + logger.log('Raw Key Derived Successfully!'); return derivedKey; } catch (error) { - elizaLogger.error("Error deriving raw key:", error); + logger.error('Error deriving raw key:', error); throw error; } } @@ -107,20 +88,18 @@ class DeriveKeyProvider { async deriveEd25519Keypair( path: string, subject: string, - agentId: string + agentId: string, ): Promise<{ keypair: Keypair; attestation: RemoteAttestationQuote }> { try { if (!path || !subject) { - elizaLogger.error( - "Path and Subject are required for key derivation" - ); + logger.error('Path and Subject are required for key derivation'); } - elizaLogger.log("Deriving Key in TEE..."); + logger.log('Deriving Key in TEE...'); const derivedKey = await this.client.deriveKey(path, subject); const uint8ArrayDerivedKey = derivedKey.asUint8Array(); - const hash = crypto.createHash("sha256"); + const hash = crypto.createHash('sha256'); hash.update(uint8ArrayDerivedKey); const seed = hash.digest(); const seedArray = new Uint8Array(seed); @@ -129,13 +108,13 @@ class DeriveKeyProvider { // Generate an attestation for the derived key data for public to verify const attestation = await this.generateDeriveKeyAttestation( agentId, - keypair.publicKey.toBase58() + keypair.publicKey.toBase58(), ); - elizaLogger.log("Key Derived Successfully!"); + logger.log('Key Derived Successfully!'); return { keypair, attestation }; } catch (error) { - elizaLogger.error("Error deriving key:", error); + logger.error('Error deriving key:', error); throw error; } } @@ -150,34 +129,28 @@ class DeriveKeyProvider { async deriveEcdsaKeypair( path: string, subject: string, - agentId: string + agentId: string, ): Promise<{ keypair: PrivateKeyAccount; attestation: RemoteAttestationQuote; }> { try { if (!path || !subject) { - elizaLogger.error( - "Path and Subject are required for key derivation" - ); + logger.error('Path and Subject are required for key derivation'); } - elizaLogger.log("Deriving ECDSA Key in TEE..."); - const deriveKeyResponse: DeriveKeyResponse = - await this.client.deriveKey(path, subject); + logger.log('Deriving ECDSA Key in TEE...'); + const deriveKeyResponse: DeriveKeyResponse = await this.client.deriveKey(path, subject); const hex = keccak256(deriveKeyResponse.asUint8Array()); const keypair: PrivateKeyAccount = privateKeyToAccount(hex); // Generate an attestation for the derived key data for public to verify - const attestation = await this.generateDeriveKeyAttestation( - agentId, - keypair.address - ); - elizaLogger.log("ECDSA Key Derived Successfully!"); + const attestation = await this.generateDeriveKeyAttestation(agentId, keypair.address); + logger.log('ECDSA Key Derived Successfully!'); return { keypair, attestation }; } catch (error) { - elizaLogger.error("Error deriving ecdsa key:", error); + logger.error('Error deriving ecdsa key:', error); throw error; } } @@ -185,42 +158,37 @@ class DeriveKeyProvider { const deriveKeyProvider: Provider = { get: async (runtime: IAgentRuntime, _message?: Memory, _state?: State) => { - const teeMode = runtime.getSetting("TEE_MODE"); + const teeMode = runtime.getSetting('TEE_MODE'); const provider = new DeriveKeyProvider(teeMode); const agentId = runtime.agentId; try { // Validate wallet configuration - if (!runtime.getSetting("WALLET_SECRET_SALT")) { - elizaLogger.error( - "Wallet secret salt is not configured in settings" - ); - return ""; + if (!runtime.getSetting('WALLET_SECRET_SALT')) { + logger.error('Wallet secret salt is not configured in settings'); + return ''; } try { - const secretSalt = - runtime.getSetting("WALLET_SECRET_SALT") || "secret_salt"; + const secretSalt = runtime.getSetting('WALLET_SECRET_SALT') || 'secret_salt'; const solanaKeypair = await provider.deriveEd25519Keypair( secretSalt, - "solana", - agentId - ); - const evmKeypair = await provider.deriveEcdsaKeypair( - secretSalt, - "evm", - agentId + 'solana', + agentId, ); + const evmKeypair = await provider.deriveEcdsaKeypair(secretSalt, 'evm', agentId); return JSON.stringify({ solana: solanaKeypair.keypair.publicKey, evm: evmKeypair.keypair.address, }); } catch (error) { - elizaLogger.error("Error creating PublicKey:", error); - return ""; + logger.error('Error creating PublicKey:', error); + return ''; } } catch (error) { - elizaLogger.error("Error in derive key provider:", error.message); - return `Failed to fetch derive key information: ${error instanceof Error ? error.message : "Unknown error"}`; + logger.error('Error in derive key provider:', error.message); + return `Failed to fetch derive key information: ${ + error instanceof Error ? error.message : 'Unknown error' + }`; } }, }; diff --git a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts index 59bfd2bb2b9..eb8b33c5a64 100644 --- a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts +++ b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts @@ -1,12 +1,6 @@ -import { - type IAgentRuntime, - type Memory, - type Provider, - type State, - elizaLogger, -} from "@elizaos/core"; -import { type TdxQuoteResponse, TappdClient, type TdxQuoteHashAlgorithms } from "@phala/dstack-sdk"; -import { type RemoteAttestationQuote, TEEMode, type RemoteAttestationMessage } from "@elizaos/core"; +import { type IAgentRuntime, type Memory, type Provider, type State, logger } from '@elizaos/core'; +import { type TdxQuoteResponse, TappdClient, type TdxQuoteHashAlgorithms } from '@phala/dstack-sdk'; +import { type RemoteAttestationQuote, TEEMode, type RemoteAttestationMessage } from '@elizaos/core'; class RemoteAttestationProvider { private client: TappdClient; @@ -17,26 +11,20 @@ class RemoteAttestationProvider { // Both LOCAL and DOCKER modes use the simulator, just with different endpoints switch (teeMode) { case TEEMode.LOCAL: - endpoint = "http://localhost:8090"; - elizaLogger.log( - "TEE: Connecting to local simulator at localhost:8090" - ); + endpoint = 'http://localhost:8090'; + logger.log('TEE: Connecting to local simulator at localhost:8090'); break; case TEEMode.DOCKER: - endpoint = "http://host.docker.internal:8090"; - elizaLogger.log( - "TEE: Connecting to simulator via Docker at host.docker.internal:8090" - ); + endpoint = 'http://host.docker.internal:8090'; + logger.log('TEE: Connecting to simulator via Docker at host.docker.internal:8090'); break; case TEEMode.PRODUCTION: endpoint = undefined; - elizaLogger.log( - "TEE: Running in production mode without simulator" - ); + logger.log('TEE: Running in production mode without simulator'); break; default: throw new Error( - `Invalid TEE_MODE: ${teeMode}. Must be one of: LOCAL, DOCKER, PRODUCTION` + `Invalid TEE_MODE: ${teeMode}. Must be one of: LOCAL, DOCKER, PRODUCTION`, ); } @@ -45,28 +33,30 @@ class RemoteAttestationProvider { async generateAttestation( reportData: string, - hashAlgorithm?: TdxQuoteHashAlgorithms + hashAlgorithm?: TdxQuoteHashAlgorithms, ): Promise { try { - elizaLogger.log("Generating attestation for: ", reportData); - const tdxQuote: TdxQuoteResponse = - await this.client.tdxQuote(reportData, hashAlgorithm); + logger.log('Generating attestation for: ', reportData); + const tdxQuote: TdxQuoteResponse = await this.client.tdxQuote( + reportData, + hashAlgorithm, + ); const rtmrs = tdxQuote.replayRtmrs(); - elizaLogger.log( - `rtmr0: ${rtmrs[0]}\nrtmr1: ${rtmrs[1]}\nrtmr2: ${rtmrs[2]}\nrtmr3: ${rtmrs[3]}f` + logger.log( + `rtmr0: ${rtmrs[0]}\nrtmr1: ${rtmrs[1]}\nrtmr2: ${rtmrs[2]}\nrtmr3: ${rtmrs[3]}f`, ); const quote: RemoteAttestationQuote = { quote: tdxQuote.quote, timestamp: Date.now(), }; - elizaLogger.log("Remote attestation quote: ", quote); + logger.log('Remote attestation quote: ', quote); return quote; } catch (error) { - console.error("Error generating remote attestation:", error); + console.error('Error generating remote attestation:', error); throw new Error( `Failed to generate TDX Quote: ${ - error instanceof Error ? error.message : "Unknown error" - }` + error instanceof Error ? error.message : 'Unknown error' + }`, ); } } @@ -75,7 +65,7 @@ class RemoteAttestationProvider { // Keep the original provider for backwards compatibility const remoteAttestationProvider: Provider = { get: async (runtime: IAgentRuntime, message: Memory, _state?: State) => { - const teeMode = runtime.getSetting("TEE_MODE"); + const teeMode = runtime.getSetting('TEE_MODE'); const provider = new RemoteAttestationProvider(teeMode); const agentId = runtime.agentId; @@ -87,17 +77,19 @@ const remoteAttestationProvider: Provider = { userId: message.userId, roomId: message.roomId, content: message.content.text, - } + }, }; - elizaLogger.log("Generating attestation for: ", JSON.stringify(attestationMessage)); - const attestation = await provider.generateAttestation(JSON.stringify(attestationMessage)); + logger.log('Generating attestation for: ', JSON.stringify(attestationMessage)); + const attestation = await provider.generateAttestation( + JSON.stringify(attestationMessage), + ); return `Your Agent's remote attestation is: ${JSON.stringify(attestation)}`; } catch (error) { - console.error("Error in remote attestation provider:", error); + console.error('Error in remote attestation provider:', error); throw new Error( `Failed to generate TDX Quote: ${ - error instanceof Error ? error.message : "Unknown error" - }` + error instanceof Error ? error.message : 'Unknown error' + }`, ); } }, diff --git a/packages/plugin-tee/src/providers/sgxAttestationProvider.ts b/packages/plugin-tee/src/providers/sgxAttestationProvider.ts index a50462f7d8b..a4117ce7bf4 100644 --- a/packages/plugin-tee/src/providers/sgxAttestationProvider.ts +++ b/packages/plugin-tee/src/providers/sgxAttestationProvider.ts @@ -1,5 +1,5 @@ -import type { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; -import type { SgxAttestation } from "@elizaos/core"; +import type { IAgentRuntime, Memory, Provider, State } from '@elizaos/core'; +import type { SgxAttestation } from '@elizaos/core'; import { promises as fs } from 'node:fs'; // Fix: Use node: protocol import { createHash } from 'node:crypto'; // Fix: Use node: protocol @@ -14,17 +14,15 @@ class SgxAttestationProvider { private readonly SGX_QUOTE_MAX_SIZE: number = 8192 * 4; private readonly SGX_TARGET_INFO_SIZE: number = 512; - private readonly MY_TARGET_INFO_PATH: string = "/dev/attestation/my_target_info"; - private readonly TARGET_INFO_PATH: string = "/dev/attestation/target_info"; - private readonly USER_REPORT_DATA_PATH: string = "/dev/attestation/user_report_data"; - private readonly QUOTE_PATH: string = "/dev/attestation/quote"; + private readonly MY_TARGET_INFO_PATH: string = '/dev/attestation/my_target_info'; + private readonly TARGET_INFO_PATH: string = '/dev/attestation/target_info'; + private readonly USER_REPORT_DATA_PATH: string = '/dev/attestation/user_report_data'; + private readonly QUOTE_PATH: string = '/dev/attestation/quote'; // Remove unnecessary constructor // constructor() {} - async generateAttestation( - reportData: string - ): Promise { + async generateAttestation(reportData: string): Promise { // Hash the report data to generate the raw user report. // The resulting hash value is 32 bytes long. // Ensure that the length of the raw user report does not exceed 64 bytes. @@ -42,25 +40,23 @@ class SgxAttestationProvider { // console.log("SGX remote attestation: ", attestation); return attestation; } catch (error) { - console.error("Error generating SGX remote attestation:", error); + console.error('Error generating SGX remote attestation:', error); throw new Error( `Failed to generate SGX Quote: ${ - error instanceof Error ? error.message : "Unknown error" - }` + error instanceof Error ? error.message : 'Unknown error' + }`, ); } } - async generateQuoteByGramine( - rawUserReport: Buffer - ): Promise { + async generateQuoteByGramine(rawUserReport: Buffer): Promise { if (rawUserReport.length > 64) { - throw new Error("the length of rawUserReport exceeds 64 bytes"); + throw new Error('the length of rawUserReport exceeds 64 bytes'); } const myTargetInfo = await fs.readFile(this.MY_TARGET_INFO_PATH); if (myTargetInfo.length !== this.SGX_TARGET_INFO_SIZE) { - throw new Error("Invalid my_target_info length"); + throw new Error('Invalid my_target_info length'); } await fs.writeFile(this.TARGET_INFO_PATH, myTargetInfo); @@ -69,12 +65,12 @@ class SgxAttestationProvider { // Read quote const quoteData = await fs.readFile(this.QUOTE_PATH); if (quoteData.length > this.SGX_QUOTE_MAX_SIZE) { - throw new Error("Invalid quote length"); + throw new Error('Invalid quote length'); } const realLen = quoteData.lastIndexOf(0); if (realLen === -1) { - throw new Error("quote without EOF"); + throw new Error('quote without EOF'); } //return '0x' + quoteData.subarray(0, realLen + 1).toString('hex'); @@ -92,11 +88,11 @@ const sgxAttestationProvider: Provider = { const attestation = await provider.generateAttestation(agentId); return `Your Agent's remote attestation is: ${JSON.stringify(attestation)}`; } catch (error) { - console.error("Error in remote attestation provider:", error); + console.error('Error in remote attestation provider:', error); throw new Error( `Failed to generate SGX Quote: ${ - error instanceof Error ? error.message : "Unknown error" - }` + error instanceof Error ? error.message : 'Unknown error' + }`, ); } }, diff --git a/packages/plugin-tee/src/providers/walletProvider.ts b/packages/plugin-tee/src/providers/walletProvider.ts index 6f0f6dddb02..24ac7943b34 100644 --- a/packages/plugin-tee/src/providers/walletProvider.ts +++ b/packages/plugin-tee/src/providers/walletProvider.ts @@ -1,26 +1,20 @@ /* This is an example of how WalletProvider can use DeriveKeyProvider to generate a Solana Keypair */ -import { - type IAgentRuntime, - type Memory, - type Provider, - type State, - elizaLogger, -} from "@elizaos/core"; -import { Connection, type Keypair, type PublicKey } from "@solana/web3.js"; -import BigNumber from "bignumber.js"; -import NodeCache from "node-cache"; -import { DeriveKeyProvider } from "./deriveKeyProvider"; -import type { RemoteAttestationQuote } from "@elizaos/core"; +import { type IAgentRuntime, type Memory, type Provider, type State, logger } from '@elizaos/core'; +import { Connection, type Keypair, type PublicKey } from '@solana/web3.js'; +import BigNumber from 'bignumber.js'; +import NodeCache from 'node-cache'; +import { DeriveKeyProvider } from './deriveKeyProvider'; +import type { RemoteAttestationQuote } from '@elizaos/core'; // Provider configuration const PROVIDER_CONFIG = { - BIRDEYE_API: "https://public-api.birdeye.so", + BIRDEYE_API: 'https://public-api.birdeye.so', MAX_RETRIES: 3, RETRY_DELAY: 2000, - DEFAULT_RPC: "https://api.mainnet-beta.solana.com", + DEFAULT_RPC: 'https://api.mainnet-beta.solana.com', TOKEN_ADDRESSES: { - SOL: "So11111111111111111111111111111111111111112", - BTC: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh", - ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + SOL: 'So11111111111111111111111111111111111111112', + BTC: '3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh', + ETH: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs', }, }; @@ -62,27 +56,23 @@ export class WalletProvider { constructor( private connection: Connection, - private walletPublicKey: PublicKey + private walletPublicKey: PublicKey, ) { this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes } - private async fetchWithRetry( - runtime, - url: string, - options: RequestInit = {} - ): Promise { + private async fetchWithRetry(runtime, url: string, options: RequestInit = {}): Promise { let lastError: Error; for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { try { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + const apiKey = runtime.getSetting('BIRDEYE_API_KEY'); const response = await fetch(url, { ...options, headers: { - Accept: "application/json", - "x-chain": "solana", - "X-API-KEY": apiKey || "", + Accept: 'application/json', + 'x-chain': 'solana', + 'X-API-KEY': apiKey || '', ...options.headers, }, }); @@ -90,27 +80,23 @@ export class WalletProvider { if (!response.ok) { const errorText = await response.text(); throw new Error( - `HTTP error! status: ${response.status}, message: ${errorText}` + `HTTP error! status: ${response.status}, message: ${errorText}`, ); } const data = await response.json(); return data; } catch (error) { - elizaLogger.error(`Attempt ${i + 1} failed:`, error); + logger.error(`Attempt ${i + 1} failed:`, error); lastError = error; if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { - const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i); + const delay = PROVIDER_CONFIG.RETRY_DELAY * 2 ** i; await new Promise((resolve) => setTimeout(resolve, delay)); - continue; } } } - elizaLogger.error( - "All attempts failed. Throwing the last error:", - lastError - ); + logger.error('All attempts failed. Throwing the last error:', lastError); throw lastError; } @@ -120,19 +106,21 @@ export class WalletProvider { const cachedValue = this.cache.get(cacheKey); if (cachedValue) { - elizaLogger.log("Cache hit for fetchPortfolioValue"); + logger.log('Cache hit for fetchPortfolioValue'); return cachedValue; } - elizaLogger.log("Cache miss for fetchPortfolioValue"); + logger.log('Cache miss for fetchPortfolioValue'); const walletData = await this.fetchWithRetry( runtime, - `${PROVIDER_CONFIG.BIRDEYE_API}/v1/wallet/token_list?wallet=${this.walletPublicKey.toBase58()}` + `${ + PROVIDER_CONFIG.BIRDEYE_API + }/v1/wallet/token_list?wallet=${this.walletPublicKey.toBase58()}`, ); if (!walletData?.success || !walletData?.data) { - elizaLogger.error("No portfolio data available", walletData); - throw new Error("No portfolio data available"); + logger.error('No portfolio data available', walletData); + throw new Error('No portfolio data available'); } const data = walletData.data; @@ -142,13 +130,11 @@ export class WalletProvider { const items = data.items.map((item: any) => ({ ...item, - valueSol: new BigNumber(item.valueUsd || 0) - .div(solPriceInUSD) - .toFixed(6), - name: item.name || "Unknown", - symbol: item.symbol || "Unknown", - priceUsd: item.priceUsd || "0", - valueUsd: item.valueUsd || "0", + valueSol: new BigNumber(item.valueUsd || 0).div(solPriceInUSD).toFixed(6), + name: item.name || 'Unknown', + symbol: item.symbol || 'Unknown', + priceUsd: item.priceUsd || '0', + valueUsd: item.valueUsd || '0', })); const totalSol = totalUsd.div(solPriceInUSD); @@ -156,36 +142,34 @@ export class WalletProvider { totalUsd: totalUsd.toString(), totalSol: totalSol.toFixed(6), items: items.sort((a, b) => - new BigNumber(b.valueUsd) - .minus(new BigNumber(a.valueUsd)) - .toNumber() + new BigNumber(b.valueUsd).minus(new BigNumber(a.valueUsd)).toNumber(), ), }; this.cache.set(cacheKey, portfolio); return portfolio; } catch (error) { - elizaLogger.error("Error fetching portfolio:", error); + logger.error('Error fetching portfolio:', error); throw error; } } async fetchPrices(runtime): Promise { try { - const cacheKey = "prices"; + const cacheKey = 'prices'; const cachedValue = this.cache.get(cacheKey); if (cachedValue) { - elizaLogger.log("Cache hit for fetchPrices"); + logger.log('Cache hit for fetchPrices'); return cachedValue; } - elizaLogger.log("Cache miss for fetchPrices"); + logger.log('Cache miss for fetchPrices'); const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; const tokens = [SOL, BTC, ETH]; const prices: Prices = { - solana: { usd: "0" }, - bitcoin: { usd: "0" }, - ethereum: { usd: "0" }, + solana: { usd: '0' }, + bitcoin: { usd: '0' }, + ethereum: { usd: '0' }, }; for (const token of tokens) { @@ -194,40 +178,29 @@ export class WalletProvider { `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`, { headers: { - "x-chain": "solana", + 'x-chain': 'solana', }, - } + }, ); if (response?.data?.value) { const price = response.data.value.toString(); - prices[ - token === SOL - ? "solana" - : token === BTC - ? "bitcoin" - : "ethereum" - ].usd = price; + prices[token === SOL ? 'solana' : token === BTC ? 'bitcoin' : 'ethereum'].usd = + price; } else { - elizaLogger.warn( - `No price data available for token: ${token}` - ); + logger.warn(`No price data available for token: ${token}`); } } this.cache.set(cacheKey, prices); return prices; } catch (error) { - elizaLogger.error("Error fetching prices:", error); + logger.error('Error fetching prices:', error); throw error; } } - formatPortfolio( - runtime, - portfolio: WalletPortfolio, - prices: Prices - ): string { + formatPortfolio(runtime, portfolio: WalletPortfolio, prices: Prices): string { let output = `${runtime.character.description}\n`; output += `Wallet Address: ${this.walletPublicKey.toBase58()}\n\n`; @@ -235,24 +208,24 @@ export class WalletProvider { const totalSolFormatted = portfolio.totalSol; output += `Total Value: $${totalUsdFormatted} (${totalSolFormatted} SOL)\n\n`; - output += "Token Balances:\n"; + output += 'Token Balances:\n'; const nonZeroItems = portfolio.items.filter((item) => - new BigNumber(item.uiAmount).isGreaterThan(0) + new BigNumber(item.uiAmount).isGreaterThan(0), ); if (nonZeroItems.length === 0) { - output += "No tokens found with non-zero balance\n"; + output += 'No tokens found with non-zero balance\n'; } else { for (const item of nonZeroItems) { const valueUsd = new BigNumber(item.valueUsd).toFixed(2); - output += `${item.name} (${item.symbol}): ${new BigNumber( - item.uiAmount - ).toFixed(6)} ($${valueUsd} | ${item.valueSol} SOL)\n`; + output += `${item.name} (${item.symbol}): ${new BigNumber(item.uiAmount).toFixed( + 6, + )} ($${valueUsd} | ${item.valueSol} SOL)\n`; } } - output += "\nMarket Prices:\n"; + output += '\nMarket Prices:\n'; output += `SOL: $${new BigNumber(prices.solana.usd).toFixed(2)}\n`; output += `BTC: $${new BigNumber(prices.bitcoin.usd).toFixed(2)}\n`; output += `ETH: $${new BigNumber(prices.ethereum.usd).toFixed(2)}\n`; @@ -269,28 +242,22 @@ export class WalletProvider { return this.formatPortfolio(runtime, portfolio, prices); } catch (error) { - elizaLogger.error("Error generating portfolio report:", error); - return "Unable to fetch wallet information. Please try again later."; + logger.error('Error generating portfolio report:', error); + return 'Unable to fetch wallet information. Please try again later.'; } } } const walletProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - _message: Memory, - _state?: State - ): Promise => { + get: async (runtime: IAgentRuntime, _message: Memory, _state?: State): Promise => { const agentId = runtime.agentId; - const teeMode = runtime.getSetting("TEE_MODE"); + const teeMode = runtime.getSetting('TEE_MODE'); const deriveKeyProvider = new DeriveKeyProvider(teeMode); try { // Validate wallet configuration - if (!runtime.getSetting("WALLET_SECRET_SALT")) { - elizaLogger.error( - "Wallet secret salt is not configured in settings" - ); - return ""; + if (!runtime.getSetting('WALLET_SECRET_SALT')) { + logger.error('Wallet secret salt is not configured in settings'); + return ''; } let publicKey: PublicKey; @@ -299,15 +266,15 @@ const walletProvider: Provider = { keypair: Keypair; attestation: RemoteAttestationQuote; } = await deriveKeyProvider.deriveEd25519Keypair( - runtime.getSetting("WALLET_SECRET_SALT"), - "solana", - agentId + runtime.getSetting('WALLET_SECRET_SALT'), + 'solana', + agentId, ); publicKey = derivedKeyPair.keypair.publicKey; - elizaLogger.log("Wallet Public Key: ", publicKey.toBase58()); + logger.log('Wallet Public Key: ', publicKey.toBase58()); } catch (error) { - elizaLogger.error("Error creating PublicKey:", error); - return ""; + logger.error('Error creating PublicKey:', error); + return ''; } const connection = new Connection(PROVIDER_CONFIG.DEFAULT_RPC); @@ -316,8 +283,10 @@ const walletProvider: Provider = { const porfolio = await provider.getFormattedPortfolio(runtime); return porfolio; } catch (error) { - elizaLogger.error("Error in wallet provider:", error.message); - return `Failed to fetch wallet information: ${error instanceof Error ? error.message : "Unknown error"}`; + logger.error('Error in wallet provider:', error.message); + return `Failed to fetch wallet information: ${ + error instanceof Error ? error.message : 'Unknown error' + }`; } }, }; diff --git a/packages/plugin-tee/src/services/teeLogManager.ts b/packages/plugin-tee/src/services/teeLogManager.ts index fcba701a810..02b1d284813 100644 --- a/packages/plugin-tee/src/services/teeLogManager.ts +++ b/packages/plugin-tee/src/services/teeLogManager.ts @@ -1,8 +1,16 @@ -import { SgxAttestationProvider } from "../providers/sgxAttestationProvider"; -import { RemoteAttestationProvider as TdxAttestationProvider } from "../providers/remoteAttestationProvider"; -import { TEEMode, TeeType, type TeeLogDAO, type TeeAgent, type TeeLog, type TeeLogQuery, type TeePageQuery } from "@elizaos/core"; -import elliptic from "elliptic"; -import { v4 } from "uuid"; +import { SgxAttestationProvider } from '../providers/sgxAttestationProvider'; +import { RemoteAttestationProvider as TdxAttestationProvider } from '../providers/remoteAttestationProvider'; +import { + type TEEMode, + TeeType, + type TeeLogDAO, + type TeeAgent, + type TeeLog, + type TeeLogQuery, + type TeePageQuery, +} from '@elizaos/core'; +import elliptic from 'elliptic'; +import { v4 } from 'uuid'; export class TeeLogManager { private teeLogDAO: TeeLogDAO; @@ -22,7 +30,7 @@ export class TeeLogManager { public async registerAgent(agentId: string, agentName: string): Promise { if (!agentId) { - throw new Error("Agent ID is required"); + throw new Error('Agent ID is required'); } const keyPair = this.generateKeyPair(); @@ -34,13 +42,13 @@ export class TeeLogManager { const new_agent = { id: v4(), agentId, - agentName: agentName || "", + agentName: agentName || '', createdAt: new Date().getTime(), publicKey, attestation, }; - console.log("registerAgent new_agent", new_agent); + console.log('registerAgent new_agent', new_agent); return this.teeLogDAO.addAgent(new_agent); } @@ -53,7 +61,13 @@ export class TeeLogManager { return this.teeLogDAO.getAgent(agentId); } - public async log(agentId: string, roomId: string, userId: string, type: string, content: string): Promise { + public async log( + agentId: string, + roomId: string, + userId: string, + type: string, + content: string, + ): Promise { const keyPair = this.keyPairs.get(agentId); if (!keyPair) { throw new Error(`Agent ${agentId} not found`); @@ -65,7 +79,7 @@ export class TeeLogManager { const messageToSign = `${agentId}|${roomId}|${userId}|${type}|${content}|${timestamp}`; // Sign the joined message - const signature = "0x" + keyPair.sign(messageToSign).toDER('hex'); + const signature = '0x' + keyPair.sign(messageToSign).toDER('hex'); return this.teeLogDAO.addLog({ id: v4(), @@ -79,7 +93,11 @@ export class TeeLogManager { }); } - public async getLogs(query: TeeLogQuery, page: number, pageSize: number): Promise> { + public async getLogs( + query: TeeLogQuery, + page: number, + pageSize: number, + ): Promise> { return this.teeLogDAO.getPagedLogs(query, page, pageSize); } @@ -99,7 +117,7 @@ export class TeeLogManager { const tdxAttestation = await tdxAttestationProvider.generateAttestation(userReport); return JSON.stringify(tdxAttestation); } else { - throw new Error("Invalid TEE type"); + throw new Error('Invalid TEE type'); } } } diff --git a/packages/plugin-tee/src/services/teeLogService.ts b/packages/plugin-tee/src/services/teeLogService.ts index 1178b4f92d4..e3503d44ec0 100644 --- a/packages/plugin-tee/src/services/teeLogService.ts +++ b/packages/plugin-tee/src/services/teeLogService.ts @@ -1,8 +1,20 @@ -import { type IAgentRuntime, Service, ServiceType, type ITeeLogService, TeeType, type TeeLogDAO, type TeeAgent, type TeeLog, type TeeLogQuery, type TeePageQuery, TEEMode } from "@elizaos/core"; -import { SqliteTeeLogDAO } from "../adapters/sqliteDAO"; -import { TeeLogManager } from "./teeLogManager"; -import Database from "better-sqlite3"; -import path from "path"; +import { + type IAgentRuntime, + Service, + ServiceType, + type ITeeLogService, + TeeType, + type TeeLogDAO, + type TeeAgent, + type TeeLog, + type TeeLogQuery, + type TeePageQuery, + TEEMode, +} from '@elizaos/core'; +import { SqliteTeeLogDAO } from '../adapters/sqliteDAO'; +import { TeeLogManager } from './teeLogManager'; +import Database from 'better-sqlite3'; +import path from 'path'; export class TeeLogService extends Service implements ITeeLogService { private dbPath: string; @@ -15,7 +27,6 @@ export class TeeLogService extends Service implements ITeeLogService { private teeLogDAO: TeeLogDAO; private teeLogManager: TeeLogManager; - getInstance(): ITeeLogService { return this; } @@ -29,21 +40,21 @@ export class TeeLogService extends Service implements ITeeLogService { return; } - const enableValues = ["true", "1", "yes", "enable", "enabled", "on"]; + const enableValues = ['true', '1', 'yes', 'enable', 'enabled', 'on']; - const enableTeeLog = runtime.getSetting("ENABLE_TEE_LOG"); + const enableTeeLog = runtime.getSetting('ENABLE_TEE_LOG'); if (enableTeeLog === null) { - throw new Error("ENABLE_TEE_LOG is not set."); + throw new Error('ENABLE_TEE_LOG is not set.'); } this.enableTeeLog = enableValues.includes(enableTeeLog.toLowerCase()); if (!this.enableTeeLog) { - console.log("TEE log is not enabled."); + console.log('TEE log is not enabled.'); return; } - const runInSgx = runtime.getSetting("SGX"); - const teeMode = runtime.getSetting("TEE_MODE"); - const walletSecretSalt = runtime.getSetting("WALLET_SECRET_SALT"); + const runInSgx = runtime.getSetting('SGX'); + const teeMode = runtime.getSetting('TEE_MODE'); + const walletSecretSalt = runtime.getSetting('WALLET_SECRET_SALT'); this.teeMode = teeMode ? TEEMode[teeMode as keyof typeof TEEMode] : TEEMode.OFF; @@ -51,17 +62,17 @@ export class TeeLogService extends Service implements ITeeLogService { const useTdxDstack = teeMode && teeMode !== TEEMode.OFF && walletSecretSalt; if (useSgxGramine && useTdxDstack) { - throw new Error("Cannot configure both SGX and TDX at the same time."); + throw new Error('Cannot configure both SGX and TDX at the same time.'); } else if (useSgxGramine) { this.teeType = TeeType.SGX_GRAMINE; } else if (useTdxDstack) { this.teeType = TeeType.TDX_DSTACK; } else { - throw new Error("Invalid TEE configuration."); + throw new Error('Invalid TEE configuration.'); } - const dbPathSetting = runtime.getSetting("TEE_LOG_DB_PATH"); - this.dbPath = dbPathSetting || path.resolve("data/tee_log.sqlite"); + const dbPathSetting = runtime.getSetting('TEE_LOG_DB_PATH'); + this.dbPath = dbPathSetting || path.resolve('data/tee_log.sqlite'); const db = new Database(this.dbPath); this.teeLogDAO = new SqliteTeeLogDAO(db); @@ -79,7 +90,13 @@ export class TeeLogService extends Service implements ITeeLogService { this.initialized = true; } - async log(agentId: string, roomId: string, userId: string, type: string, content: string): Promise { + async log( + agentId: string, + roomId: string, + userId: string, + type: string, + content: string, + ): Promise { if (!this.enableTeeLog) { return false; } @@ -103,7 +120,11 @@ export class TeeLogService extends Service implements ITeeLogService { return this.teeLogManager.getAgent(agentId); } - async getLogs(query: TeeLogQuery, page: number, pageSize: number): Promise> { + async getLogs( + query: TeeLogQuery, + page: number, + pageSize: number, + ): Promise> { if (!this.enableTeeLog) { return { data: [], diff --git a/packages/plugin-tee/src/types.ts b/packages/plugin-tee/src/types.ts index 3f4bee57aac..c676f0fae84 100644 --- a/packages/plugin-tee/src/types.ts +++ b/packages/plugin-tee/src/types.ts @@ -1,6 +1,6 @@ export enum TeeType { - SGX_GRAMINE = "sgx_gramine", - TDX_DSTACK = "tdx_dstack", + SGX_GRAMINE = 'sgx_gramine', + TDX_DSTACK = 'tdx_dstack', } // Represents a log entry in the TeeLog table, containing details about agent activities. @@ -55,7 +55,7 @@ export abstract class TeeLogDAO { abstract getPagedLogs( query: TeeLogQuery, page: number, - pageSize: number + pageSize: number, ): Promise>; abstract addAgent(agent: TeeAgent): Promise; @@ -63,4 +63,4 @@ export abstract class TeeLogDAO { abstract getAgent(agentId: string): Promise; abstract getAllAgents(): Promise; -} \ No newline at end of file +} diff --git a/packages/plugin-telegram/biome.json b/packages/plugin-telegram/biome.json new file mode 100644 index 00000000000..d7e0349301a --- /dev/null +++ b/packages/plugin-telegram/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-telegram/src/actions/reply.ts b/packages/plugin-telegram/src/actions/reply.ts index 5cdc959ad4d..9dd788067d5 100644 --- a/packages/plugin-telegram/src/actions/reply.ts +++ b/packages/plugin-telegram/src/actions/reply.ts @@ -1,9 +1,9 @@ -import { - type Action, - type HandlerCallback, - type IAgentRuntime, - type Memory, - type State, +import type { + Action, + HandlerCallback, + IAgentRuntime, + Memory, + State, } from "@elizaos/core"; const replyAction = { diff --git a/packages/plugin-twitter/biome.json b/packages/plugin-twitter/biome.json new file mode 100644 index 00000000000..d7e0349301a --- /dev/null +++ b/packages/plugin-twitter/biome.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "extends": ["../../biome.json"], + "organizeImports": { + "enabled": false + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + }, + "files": { + "ignore": [ + "dist/**/*", + "extra/**/*", + "node_modules/**/*" + ] + } +} \ No newline at end of file diff --git a/packages/plugin-twitter/src/base.ts b/packages/plugin-twitter/src/base.ts index 849d395bf34..a55a5ca37ee 100644 --- a/packages/plugin-twitter/src/base.ts +++ b/packages/plugin-twitter/src/base.ts @@ -70,7 +70,7 @@ class RequestQueue { } private async exponentialBackoff(retryCount: number): Promise { - const delay = Math.pow(2, retryCount) * 1000; + const delay = (2 ** retryCount) * 1000; await new Promise((resolve) => setTimeout(resolve, delay)); } diff --git a/packages/plugin-twitter/src/client/api.ts b/packages/plugin-twitter/src/client/api.ts index 08d9f4ced7f..5e40d19ae60 100644 --- a/packages/plugin-twitter/src/client/api.ts +++ b/packages/plugin-twitter/src/client/api.ts @@ -1,6 +1,6 @@ -import { TwitterAuth } from './auth'; +import type { TwitterAuth } from './auth'; import { ApiError } from './errors'; -import { Platform, PlatformExtensions } from './platform'; +import { Platform, type PlatformExtensions } from './platform'; import { updateCookieJar } from './requests'; import { Headers } from 'headers-polyfill'; @@ -89,7 +89,7 @@ export async function requestApi( const xRateLimitReset = res.headers.get('x-rate-limit-reset'); if (xRateLimitRemaining == '0' && xRateLimitReset) { const currentTime = new Date().valueOf() / 1000; - const timeDeltaMs = 1000 * (parseInt(xRateLimitReset) - currentTime); + const timeDeltaMs = 1000 * (Number.parseInt(xRateLimitReset) - currentTime); // I have seen this block for 800s (~13 *minutes*) await new Promise((resolve) => setTimeout(resolve, timeDeltaMs)); diff --git a/packages/plugin-twitter/src/client/auth-user.ts b/packages/plugin-twitter/src/client/auth-user.ts index 4898047e511..08b36f6e111 100644 --- a/packages/plugin-twitter/src/client/auth-user.ts +++ b/packages/plugin-twitter/src/client/auth-user.ts @@ -1,13 +1,13 @@ -import { TwitterAuthOptions, TwitterGuestAuth } from './auth'; +import { type TwitterAuthOptions, TwitterGuestAuth } from './auth'; import { requestApi } from './api'; import { CookieJar } from 'tough-cookie'; import { updateCookieJar } from './requests'; import { Headers } from 'headers-polyfill'; -import { TwitterApiErrorRaw } from './errors'; +import type { TwitterApiErrorRaw } from './errors'; import { Type, type Static } from '@sinclair/typebox'; import { Check } from '@sinclair/typebox/value'; import * as OTPAuth from 'otpauth'; -import { LegacyUserRaw, parseProfile, type Profile } from './profile'; +import { type LegacyUserRaw, parseProfile, type Profile } from './profile'; interface TwitterUserAuthFlowInitRequest { flow_name: string; diff --git a/packages/plugin-twitter/src/client/auth.ts b/packages/plugin-twitter/src/client/auth.ts index 96cf3801d2c..4508a9765e9 100644 --- a/packages/plugin-twitter/src/client/auth.ts +++ b/packages/plugin-twitter/src/client/auth.ts @@ -1,9 +1,9 @@ -import { Cookie, CookieJar, MemoryCookieStore } from 'tough-cookie'; +import { type Cookie, CookieJar, type MemoryCookieStore } from 'tough-cookie'; import { updateCookieJar } from './requests'; import { Headers } from 'headers-polyfill'; -import { FetchTransformOptions } from './api'; +import type { FetchTransformOptions } from './api'; import { TwitterApi } from 'twitter-api-v2'; -import { Profile } from './profile'; +import type { Profile } from './profile'; export interface TwitterAuthOptions { fetch: typeof fetch; diff --git a/packages/plugin-twitter/src/client/command.ts b/packages/plugin-twitter/src/client/command.ts index 76e21660522..7b4ab0f61f9 100644 --- a/packages/plugin-twitter/src/client/command.ts +++ b/packages/plugin-twitter/src/client/command.ts @@ -1,6 +1,6 @@ // Your existing imports import { Scraper } from './scraper'; -import { Photo, Tweet } from './tweets'; +import type { Photo, Tweet } from './tweets'; import fs from 'fs'; import path from 'path'; import readline from 'readline'; diff --git a/packages/plugin-twitter/src/client/grok.ts b/packages/plugin-twitter/src/client/grok.ts index b39b1c780db..31bd68d3252 100644 --- a/packages/plugin-twitter/src/client/grok.ts +++ b/packages/plugin-twitter/src/client/grok.ts @@ -1,5 +1,5 @@ import { requestApi } from './api'; -import { TwitterAuth } from './auth'; +import type { TwitterAuth } from './auth'; export interface GrokConversation { data: { diff --git a/packages/plugin-twitter/src/client/messages.ts b/packages/plugin-twitter/src/client/messages.ts index 6975320d8b7..a7028db4857 100644 --- a/packages/plugin-twitter/src/client/messages.ts +++ b/packages/plugin-twitter/src/client/messages.ts @@ -1,4 +1,4 @@ -import { TwitterAuth } from './auth'; +import type { TwitterAuth } from './auth'; import { updateCookieJar } from './requests'; export interface DirectMessage { diff --git a/packages/plugin-twitter/src/client/platform/index.ts b/packages/plugin-twitter/src/client/platform/index.ts index fd0abc2a3fb..24625a7e232 100644 --- a/packages/plugin-twitter/src/client/platform/index.ts +++ b/packages/plugin-twitter/src/client/platform/index.ts @@ -1,4 +1,4 @@ -import { PlatformExtensions, genericPlatform } from './platform-interface.js'; +import { type PlatformExtensions, genericPlatform } from './platform-interface.js'; export * from './platform-interface.js'; diff --git a/packages/plugin-twitter/src/client/platform/node/index.ts b/packages/plugin-twitter/src/client/platform/node/index.ts index b4a19ed92a4..50a08ee5107 100644 --- a/packages/plugin-twitter/src/client/platform/node/index.ts +++ b/packages/plugin-twitter/src/client/platform/node/index.ts @@ -1,4 +1,4 @@ -import { PlatformExtensions } from '../platform-interface'; +import type { PlatformExtensions } from '../platform-interface'; import { randomizeCiphers } from './randomize-ciphers'; class NodePlatform implements PlatformExtensions { diff --git a/packages/plugin-twitter/src/client/profile.test.ts b/packages/plugin-twitter/src/client/profile.test.ts index b7a876f13dd..257748f5b04 100644 --- a/packages/plugin-twitter/src/client/profile.test.ts +++ b/packages/plugin-twitter/src/client/profile.test.ts @@ -1,4 +1,4 @@ -import { Profile } from './profile'; +import type { Profile } from './profile'; import { getScraper } from './test-utils'; test('scraper can get screen name by user id', async () => { diff --git a/packages/plugin-twitter/src/client/profile.ts b/packages/plugin-twitter/src/client/profile.ts index 1c178439380..361013951bb 100644 --- a/packages/plugin-twitter/src/client/profile.ts +++ b/packages/plugin-twitter/src/client/profile.ts @@ -1,7 +1,7 @@ import stringify from 'json-stable-stringify'; -import { requestApi, RequestApiResult } from './api'; -import { TwitterAuth } from './auth'; -import { TwitterApiErrorRaw } from './errors'; +import { requestApi, type RequestApiResult } from './api'; +import type { TwitterAuth } from './auth'; +import type { TwitterApiErrorRaw } from './errors'; export interface LegacyUserRaw { created_at?: string; diff --git a/packages/plugin-twitter/src/client/relationships.ts b/packages/plugin-twitter/src/client/relationships.ts index f1b8b0d5fe1..969f3103c0b 100644 --- a/packages/plugin-twitter/src/client/relationships.ts +++ b/packages/plugin-twitter/src/client/relationships.ts @@ -1,11 +1,11 @@ import { addApiFeatures, requestApi, bearerToken } from './api'; import { Headers } from 'headers-polyfill'; -import { TwitterAuth } from './auth'; -import { Profile, getUserIdByScreenName } from './profile'; -import { QueryProfilesResponse } from './timeline-v1'; +import type { TwitterAuth } from './auth'; +import { type Profile, getUserIdByScreenName } from './profile'; +import type { QueryProfilesResponse } from './timeline-v1'; import { getUserTimeline } from './timeline-async'; import { - RelationshipTimeline, + type RelationshipTimeline, parseRelationshipTimeline, } from './timeline-relationship'; import stringify from 'json-stable-stringify'; diff --git a/packages/plugin-twitter/src/client/requests.ts b/packages/plugin-twitter/src/client/requests.ts index aba633ac629..54d05a22abb 100644 --- a/packages/plugin-twitter/src/client/requests.ts +++ b/packages/plugin-twitter/src/client/requests.ts @@ -1,4 +1,4 @@ -import { Cookie, CookieJar } from 'tough-cookie'; +import { Cookie, type CookieJar } from 'tough-cookie'; import setCookie from 'set-cookie-parser'; import type { Headers as HeadersPolyfill } from 'headers-polyfill'; diff --git a/packages/plugin-twitter/src/client/scraper.ts b/packages/plugin-twitter/src/client/scraper.ts index 9546f3f0902..5a23d1b31ce 100644 --- a/packages/plugin-twitter/src/client/scraper.ts +++ b/packages/plugin-twitter/src/client/scraper.ts @@ -1,17 +1,17 @@ -import { Cookie } from 'tough-cookie'; +import type { Cookie } from 'tough-cookie'; import { bearerToken, - FetchTransformOptions, + type FetchTransformOptions, requestApi, - RequestApiResult, + type RequestApiResult, } from './api'; -import { TwitterAuth, TwitterAuthOptions, TwitterGuestAuth } from './auth'; +import { type TwitterAuth, type TwitterAuthOptions, TwitterGuestAuth } from './auth'; import { TwitterUserAuth } from './auth-user'; import { getProfile, getUserIdByScreenName, getScreenNameByUserId, - Profile, + type Profile, } from './profile'; import { fetchQuotedTweetsPage, @@ -29,23 +29,23 @@ import { getFollowers, followUser, } from './relationships'; -import { QueryProfilesResponse, QueryTweetsResponse } from './timeline-v1'; +import type { QueryProfilesResponse, QueryTweetsResponse } from './timeline-v1'; import { getTrends } from './trends'; import { - Tweet, + type Tweet, getTweetAnonymous, getTweets, getLatestTweet, getTweetWhere, getTweetsWhere, getTweetsByUserId, - TweetQuery, + type TweetQuery, getTweet, fetchListTweets, getTweetsAndRepliesByUserId, getTweetsAndReplies, createCreateTweetRequest, - PollData, + type PollData, createCreateTweetRequestV2, getTweetV2, getTweetsV2, @@ -57,16 +57,16 @@ import { createCreateLongTweetRequest, getArticle, getAllRetweeters, - Retweeter, + type Retweeter, } from './tweets'; import { parseTimelineTweetsV2, - TimelineArticle, - TimelineV2, + type TimelineArticle, + type TimelineV2, } from './timeline-v2'; import { fetchHomeTimeline } from './timeline-home'; import { fetchFollowingTimeline } from './timeline-following'; -import { +import type { TTweetv2Expansion, TTweetv2MediaField, TTweetv2PlaceField, @@ -75,10 +75,10 @@ import { TTweetv2UserField, } from 'twitter-api-v2'; import { - DirectMessagesResponse, + type DirectMessagesResponse, getDirectMessageConversations, sendDirectMessage, - SendDirectMessageResponse, + type SendDirectMessageResponse, } from './messages'; import { fetchAudioSpaceById, @@ -88,7 +88,7 @@ import { fetchLiveVideoStreamStatus, fetchLoginTwitterToken, } from './spaces'; -import { +import type { AudioSpace, Community, LiveVideoStreamStatus, @@ -98,8 +98,8 @@ import { import { createGrokConversation, grokChat, - GrokChatOptions, - GrokChatResponse, + type GrokChatOptions, + type GrokChatResponse, } from './grok'; const twUrl = 'https://twitter.com'; diff --git a/packages/plugin-twitter/src/client/search.test.ts b/packages/plugin-twitter/src/client/search.test.ts index bf60fceb86a..d1a03d42670 100644 --- a/packages/plugin-twitter/src/client/search.test.ts +++ b/packages/plugin-twitter/src/client/search.test.ts @@ -1,6 +1,6 @@ import { getScraper } from './test-utils'; import { SearchMode } from './search'; -import { QueryTweetsResponse } from './timeline-v1'; +import type { QueryTweetsResponse } from './timeline-v1'; test('scraper can process search cursor', async () => { const scraper = await getScraper(); diff --git a/packages/plugin-twitter/src/client/search.ts b/packages/plugin-twitter/src/client/search.ts index 3682764c048..6041ab4c9a7 100644 --- a/packages/plugin-twitter/src/client/search.ts +++ b/packages/plugin-twitter/src/client/search.ts @@ -1,11 +1,11 @@ import { addApiFeatures, requestApi } from './api'; -import { TwitterAuth } from './auth'; -import { Profile } from './profile'; -import { QueryProfilesResponse, QueryTweetsResponse } from './timeline-v1'; +import type { TwitterAuth } from './auth'; +import type { Profile } from './profile'; +import type { QueryProfilesResponse, QueryTweetsResponse } from './timeline-v1'; import { getTweetTimeline, getUserTimeline } from './timeline-async'; -import { Tweet } from './tweets'; +import type { Tweet } from './tweets'; import { - SearchTimeline, + type SearchTimeline, parseSearchTimelineTweets, parseSearchTimelineUsers, } from './timeline-search'; @@ -15,11 +15,11 @@ import stringify from 'json-stable-stringify'; * The categories that can be used in Twitter searches. */ export enum SearchMode { - Top, - Latest, - Photos, - Videos, - Users, + Top = 0, + Latest = 1, + Photos = 2, + Videos = 3, + Users = 4, } export function searchTweets( diff --git a/packages/plugin-twitter/src/client/spaces.ts b/packages/plugin-twitter/src/client/spaces.ts index 5d0acfb8766..ff6c5dff638 100644 --- a/packages/plugin-twitter/src/client/spaces.ts +++ b/packages/plugin-twitter/src/client/spaces.ts @@ -1,6 +1,6 @@ -import { TwitterAuth } from './auth'; +import type { TwitterAuth } from './auth'; import { updateCookieJar } from './requests'; -import { +import type { AudioSpace, AudioSpaceByIdResponse, AudioSpaceByIdVariables, diff --git a/packages/plugin-twitter/src/client/spaces/core/ChatClient.ts b/packages/plugin-twitter/src/client/spaces/core/ChatClient.ts index 2117beb867e..9b2053522c8 100644 --- a/packages/plugin-twitter/src/client/spaces/core/ChatClient.ts +++ b/packages/plugin-twitter/src/client/spaces/core/ChatClient.ts @@ -3,7 +3,7 @@ import WebSocket from 'ws'; import { EventEmitter } from 'events'; import type { SpeakerRequest, OccupancyUpdate } from '../types'; -import { Logger } from '../logger'; +import type { Logger } from '../logger'; /** * Configuration object for ChatClient. diff --git a/packages/plugin-twitter/src/client/spaces/core/JanusAudio.ts b/packages/plugin-twitter/src/client/spaces/core/JanusAudio.ts index 99626be6f46..0e4d8ad641a 100644 --- a/packages/plugin-twitter/src/client/spaces/core/JanusAudio.ts +++ b/packages/plugin-twitter/src/client/spaces/core/JanusAudio.ts @@ -4,7 +4,7 @@ import { EventEmitter } from 'events'; import wrtc from '@roamhq/wrtc'; const { nonstandard } = wrtc; const { RTCAudioSource, RTCAudioSink } = nonstandard; -import { Logger } from '../logger'; +import type { Logger } from '../logger'; /** * Configuration options for the JanusAudioSource. diff --git a/packages/plugin-twitter/src/client/spaces/core/JanusClient.ts b/packages/plugin-twitter/src/client/spaces/core/JanusClient.ts index 65cdfac46fe..a965b7c4bf7 100644 --- a/packages/plugin-twitter/src/client/spaces/core/JanusClient.ts +++ b/packages/plugin-twitter/src/client/spaces/core/JanusClient.ts @@ -5,7 +5,7 @@ import wrtc from '@roamhq/wrtc'; const { RTCPeerConnection, MediaStream } = wrtc; import { JanusAudioSink, JanusAudioSource } from './JanusAudio'; import type { AudioDataWithUser, TurnServersInfo } from '../types'; -import { Logger } from '../logger'; +import type { Logger } from '../logger'; interface JanusConfig { /** @@ -213,7 +213,7 @@ export class JanusClient extends EventEmitter { */ public async subscribeSpeaker( userId: string, - feedId: number = 0, + feedId = 0, ): Promise { this.logger.debug('[JanusClient] subscribeSpeaker => userId=', userId); @@ -569,7 +569,7 @@ export class JanusClient extends EventEmitter { * Creates an SDP offer and sends "configure" to Janus with it. * Used by both host and guest after attach + join. */ - private async configurePublisher(sessionUUID: string = ''): Promise { + private async configurePublisher(sessionUUID = ''): Promise { if (!this.pc || !this.sessionId || !this.handleId) { return; } diff --git a/packages/plugin-twitter/src/client/spaces/core/Space.ts b/packages/plugin-twitter/src/client/spaces/core/Space.ts index 42a44739234..73a4f5a1982 100644 --- a/packages/plugin-twitter/src/client/spaces/core/Space.ts +++ b/packages/plugin-twitter/src/client/spaces/core/Space.ts @@ -21,7 +21,7 @@ import type { SpeakerInfo, SpaceConfig, } from '../types'; -import { Scraper } from '../../scraper'; +import type { Scraper } from '../../scraper'; import { Logger } from '../logger'; /** diff --git a/packages/plugin-twitter/src/client/spaces/core/SpaceParticipant.ts b/packages/plugin-twitter/src/client/spaces/core/SpaceParticipant.ts index 84679ee7aae..5bd7eb171a0 100644 --- a/packages/plugin-twitter/src/client/spaces/core/SpaceParticipant.ts +++ b/packages/plugin-twitter/src/client/spaces/core/SpaceParticipant.ts @@ -4,7 +4,7 @@ import { EventEmitter } from 'events'; import { Logger } from '../logger'; import { ChatClient } from './ChatClient'; import { JanusClient } from './JanusClient'; -import { Scraper } from '../../scraper'; +import type { Scraper } from '../../scraper'; import type { TurnServersInfo, Plugin, diff --git a/packages/plugin-twitter/src/client/spaces/plugins/HlsRecordPlugin.ts b/packages/plugin-twitter/src/client/spaces/plugins/HlsRecordPlugin.ts index 8b860c51d8a..946cad9a4d1 100644 --- a/packages/plugin-twitter/src/client/spaces/plugins/HlsRecordPlugin.ts +++ b/packages/plugin-twitter/src/client/spaces/plugins/HlsRecordPlugin.ts @@ -1,6 +1,6 @@ -import { spawn, ChildProcessWithoutNullStreams } from 'child_process'; -import { Plugin, OccupancyUpdate } from '../types'; -import { Space } from '../core/Space'; +import { spawn, type ChildProcessWithoutNullStreams } from 'child_process'; +import type { Plugin, OccupancyUpdate } from '../types'; +import type { Space } from '../core/Space'; import { Logger } from '../logger'; /** diff --git a/packages/plugin-twitter/src/client/spaces/plugins/IdleMonitorPlugin.ts b/packages/plugin-twitter/src/client/spaces/plugins/IdleMonitorPlugin.ts index 07c9e77a43e..3128e00507e 100644 --- a/packages/plugin-twitter/src/client/spaces/plugins/IdleMonitorPlugin.ts +++ b/packages/plugin-twitter/src/client/spaces/plugins/IdleMonitorPlugin.ts @@ -1,5 +1,5 @@ -import { Plugin, AudioDataWithUser } from '../types'; -import { Space } from '../core/Space'; +import type { Plugin, AudioDataWithUser } from '../types'; +import type { Space } from '../core/Space'; import { Logger } from '../logger'; /** @@ -21,8 +21,8 @@ export class IdleMonitorPlugin implements Plugin { * @param checkEveryMs How frequently (in ms) to check for silence. (Default: 10s) */ constructor( - private idleTimeoutMs: number = 60_000, - private checkEveryMs: number = 10_000, + private idleTimeoutMs = 60_000, + private checkEveryMs = 10_000, ) {} /** diff --git a/packages/plugin-twitter/src/client/spaces/plugins/MonitorAudioPlugin.ts b/packages/plugin-twitter/src/client/spaces/plugins/MonitorAudioPlugin.ts index 631d0e3d181..3ec261a12ad 100644 --- a/packages/plugin-twitter/src/client/spaces/plugins/MonitorAudioPlugin.ts +++ b/packages/plugin-twitter/src/client/spaces/plugins/MonitorAudioPlugin.ts @@ -1,5 +1,5 @@ -import { spawn, ChildProcessWithoutNullStreams } from 'child_process'; -import { Plugin, AudioDataWithUser } from '../types'; +import { spawn, type ChildProcessWithoutNullStreams } from 'child_process'; +import type { Plugin, AudioDataWithUser } from '../types'; import { Logger } from '../logger'; /** diff --git a/packages/plugin-twitter/src/client/spaces/plugins/RecordToDiskPlugin.ts b/packages/plugin-twitter/src/client/spaces/plugins/RecordToDiskPlugin.ts index 784eddefa7a..9e45baf5b5c 100644 --- a/packages/plugin-twitter/src/client/spaces/plugins/RecordToDiskPlugin.ts +++ b/packages/plugin-twitter/src/client/spaces/plugins/RecordToDiskPlugin.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; -import { AudioDataWithUser, Plugin } from '../types'; -import { Space } from '../core/Space'; -import { SpaceParticipant } from '../core/SpaceParticipant'; +import type { AudioDataWithUser, Plugin } from '../types'; +import type { Space } from '../core/Space'; +import type { SpaceParticipant } from '../core/SpaceParticipant'; import { Logger } from '../logger'; interface RecordToDiskPluginConfig { @@ -21,7 +21,7 @@ interface RecordToDiskPluginConfig { * - cleanup(...) => close file stream */ export class RecordToDiskPlugin implements Plugin { - private filePath: string = '/tmp/speaker_audio.raw'; + private filePath = '/tmp/speaker_audio.raw'; private outStream?: fs.WriteStream; private logger?: Logger; diff --git a/packages/plugin-twitter/src/client/spaces/plugins/SttTtsPlugin.ts b/packages/plugin-twitter/src/client/spaces/plugins/SttTtsPlugin.ts index 8cf07eb2982..b84c15654ff 100644 --- a/packages/plugin-twitter/src/client/spaces/plugins/SttTtsPlugin.ts +++ b/packages/plugin-twitter/src/client/spaces/plugins/SttTtsPlugin.ts @@ -3,10 +3,10 @@ import fs from 'fs'; import path from 'path'; import { spawn } from 'child_process'; -import { AudioDataWithUser, Plugin } from '../types'; -import { Space } from '../core/Space'; -import { SpaceParticipant } from '../core/SpaceParticipant'; -import { JanusClient } from '../core/JanusClient'; +import type { AudioDataWithUser, Plugin } from '../types'; +import type { Space } from '../core/Space'; +import type { SpaceParticipant } from '../core/SpaceParticipant'; +import type { JanusClient } from '../core/JanusClient'; import { Logger } from '../logger'; interface PluginConfig { @@ -52,12 +52,12 @@ export class SttTtsPlugin implements Plugin { // Credentials & config private openAiApiKey?: string; private elevenLabsApiKey?: string; - private sttLanguage: string = 'en'; - private gptModel: string = 'gpt-3.5-turbo'; - private voiceId: string = '21m00Tcm4TlvDq8ikWAM'; - private elevenLabsModel: string = 'eleven_monolingual_v1'; - private systemPrompt: string = 'You are a helpful AI assistant.'; - private silenceThreshold: number = 50; + private sttLanguage = 'en'; + private gptModel = 'gpt-3.5-turbo'; + private voiceId = '21m00Tcm4TlvDq8ikWAM'; + private elevenLabsModel = 'eleven_monolingual_v1'; + private systemPrompt = 'You are a helpful AI assistant.'; + private silenceThreshold = 50; /** * chatContext accumulates the conversation for GPT: @@ -85,7 +85,7 @@ export class SttTtsPlugin implements Plugin { * TTS queue for sequential playback */ private ttsQueue: string[] = []; - private isSpeaking: boolean = false; + private isSpeaking = false; /** * Called immediately after `.use(plugin)`. diff --git a/packages/plugin-twitter/src/client/spaces/test.ts b/packages/plugin-twitter/src/client/spaces/test.ts index b22803eb258..c2393d27c21 100644 --- a/packages/plugin-twitter/src/client/spaces/test.ts +++ b/packages/plugin-twitter/src/client/spaces/test.ts @@ -1,7 +1,7 @@ // src/test.ts import 'dotenv/config'; -import { Space, SpaceConfig } from './core/Space'; +import { Space, type SpaceConfig } from './core/Space'; import { Scraper } from '../scraper'; import { RecordToDiskPlugin } from './plugins/RecordToDiskPlugin'; import { SttTtsPlugin } from './plugins/SttTtsPlugin'; diff --git a/packages/plugin-twitter/src/client/spaces/types.ts b/packages/plugin-twitter/src/client/spaces/types.ts index 9b59fa50c83..da6f3940c2d 100644 --- a/packages/plugin-twitter/src/client/spaces/types.ts +++ b/packages/plugin-twitter/src/client/spaces/types.ts @@ -1,7 +1,7 @@ // src/types.ts -import { Space } from './core/Space'; -import { SpaceParticipant } from './core/SpaceParticipant'; +import type { Space } from './core/Space'; +import type { SpaceParticipant } from './core/SpaceParticipant'; /** * Basic PCM audio frame properties. diff --git a/packages/plugin-twitter/src/client/spaces/utils.ts b/packages/plugin-twitter/src/client/spaces/utils.ts index 83b8eb9468a..e98a4075af2 100644 --- a/packages/plugin-twitter/src/client/spaces/utils.ts +++ b/packages/plugin-twitter/src/client/spaces/utils.ts @@ -2,9 +2,9 @@ import { Headers } from 'headers-polyfill'; import type { BroadcastCreated, TurnServersInfo } from './types'; -import { ChatClient } from './core/ChatClient'; -import { Logger } from './logger'; -import { EventEmitter } from 'events'; +import type { ChatClient } from './core/ChatClient'; +import type { Logger } from './logger'; +import type { EventEmitter } from 'events'; /** * Authorizes a token for guest access, using the provided Periscope cookie. diff --git a/packages/plugin-twitter/src/client/timeline-async.ts b/packages/plugin-twitter/src/client/timeline-async.ts index e92f7c454a0..e06cef9bb28 100644 --- a/packages/plugin-twitter/src/client/timeline-async.ts +++ b/packages/plugin-twitter/src/client/timeline-async.ts @@ -1,5 +1,5 @@ -import { Profile } from './profile'; -import { Tweet } from './tweets'; +import type { Profile } from './profile'; +import type { Tweet } from './tweets'; export interface FetchProfilesResponse { profiles: Profile[]; diff --git a/packages/plugin-twitter/src/client/timeline-following.ts b/packages/plugin-twitter/src/client/timeline-following.ts index 8c308330a8c..966ec78a147 100644 --- a/packages/plugin-twitter/src/client/timeline-following.ts +++ b/packages/plugin-twitter/src/client/timeline-following.ts @@ -1,7 +1,7 @@ import { requestApi } from './api'; -import { TwitterAuth } from './auth'; +import type { TwitterAuth } from './auth'; import { ApiError } from './errors'; -import { TimelineInstruction } from './timeline-v2'; +import type { TimelineInstruction } from './timeline-v2'; export interface HomeLatestTimelineResponse { data?: { diff --git a/packages/plugin-twitter/src/client/timeline-home.ts b/packages/plugin-twitter/src/client/timeline-home.ts index 301137255cd..e090a9d6a3f 100644 --- a/packages/plugin-twitter/src/client/timeline-home.ts +++ b/packages/plugin-twitter/src/client/timeline-home.ts @@ -1,7 +1,7 @@ import { requestApi } from './api'; -import { TwitterAuth } from './auth'; +import type { TwitterAuth } from './auth'; import { ApiError } from './errors'; -import { TimelineInstruction } from './timeline-v2'; +import type { TimelineInstruction } from './timeline-v2'; export interface HomeTimelineResponse { data?: { diff --git a/packages/plugin-twitter/src/client/timeline-list.ts b/packages/plugin-twitter/src/client/timeline-list.ts index 6b7b0cd356f..b31bc2d2a0b 100644 --- a/packages/plugin-twitter/src/client/timeline-list.ts +++ b/packages/plugin-twitter/src/client/timeline-list.ts @@ -1,6 +1,6 @@ -import { QueryTweetsResponse } from './timeline-v1'; -import { parseAndPush, TimelineEntryRaw } from './timeline-v2'; -import { Tweet } from './tweets'; +import type { QueryTweetsResponse } from './timeline-v1'; +import { parseAndPush, type TimelineEntryRaw } from './timeline-v2'; +import type { Tweet } from './tweets'; export interface ListTimeline { data?: { diff --git a/packages/plugin-twitter/src/client/timeline-relationship.ts b/packages/plugin-twitter/src/client/timeline-relationship.ts index ed5a7db094c..b3cf18c1689 100644 --- a/packages/plugin-twitter/src/client/timeline-relationship.ts +++ b/packages/plugin-twitter/src/client/timeline-relationship.ts @@ -1,6 +1,6 @@ -import { Profile, parseProfile } from './profile'; -import { QueryProfilesResponse } from './timeline-v1'; -import { TimelineUserResultRaw } from './timeline-v2'; +import { type Profile, parseProfile } from './profile'; +import type { QueryProfilesResponse } from './timeline-v1'; +import type { TimelineUserResultRaw } from './timeline-v2'; export interface RelationshipEntryItemContentRaw { itemType?: string; diff --git a/packages/plugin-twitter/src/client/timeline-search.ts b/packages/plugin-twitter/src/client/timeline-search.ts index 530f17206a0..e9784223a33 100644 --- a/packages/plugin-twitter/src/client/timeline-search.ts +++ b/packages/plugin-twitter/src/client/timeline-search.ts @@ -1,7 +1,7 @@ -import { Profile, parseProfile } from './profile'; -import { QueryProfilesResponse, QueryTweetsResponse } from './timeline-v1'; -import { SearchEntryRaw, parseLegacyTweet } from './timeline-v2'; -import { Tweet } from './tweets'; +import { type Profile, parseProfile } from './profile'; +import type { QueryProfilesResponse, QueryTweetsResponse } from './timeline-v1'; +import { type SearchEntryRaw, parseLegacyTweet } from './timeline-v2'; +import type { Tweet } from './tweets'; export interface SearchTimeline { data?: { @@ -53,7 +53,7 @@ export function parseSearchTimelineTweets( if (tweetResult.success) { if (!tweetResult.tweet.views && tweetResultRaw?.views?.count) { - const views = parseInt(tweetResultRaw.views.count); + const views = Number.parseInt(tweetResultRaw.views.count); if (!isNaN(views)) { tweetResult.tweet.views = views; } diff --git a/packages/plugin-twitter/src/client/timeline-tweet-util.ts b/packages/plugin-twitter/src/client/timeline-tweet-util.ts index 762416cc61d..6547ec88eb8 100644 --- a/packages/plugin-twitter/src/client/timeline-tweet-util.ts +++ b/packages/plugin-twitter/src/client/timeline-tweet-util.ts @@ -1,6 +1,6 @@ -import { LegacyTweetRaw, TimelineMediaExtendedRaw } from './timeline-v1'; -import { Photo, Video } from './tweets'; -import { isFieldDefined, NonNullableField } from './type-util'; +import type { LegacyTweetRaw, TimelineMediaExtendedRaw } from './timeline-v1'; +import type { Photo, Video } from './tweets'; +import { isFieldDefined, type NonNullableField } from './type-util'; const reHashtag = /\B(\#\S+\b)/g; const reCashtag = /\B(\$\S+\b)/g; @@ -127,7 +127,7 @@ function linkUsernameHtml(username: string) { } function unwrapTcoUrlHtml(tweet: LegacyTweetRaw, foundedMedia: string[]) { - return function (tco: string) { + return (tco: string) => { for (const entity of tweet.entities?.urls ?? []) { if (tco === entity.url && entity.expanded_url != null) { return `${tco}`; diff --git a/packages/plugin-twitter/src/client/timeline-v1.ts b/packages/plugin-twitter/src/client/timeline-v1.ts index 17395805f53..298c26063b1 100644 --- a/packages/plugin-twitter/src/client/timeline-v1.ts +++ b/packages/plugin-twitter/src/client/timeline-v1.ts @@ -1,6 +1,6 @@ -import { LegacyUserRaw, parseProfile, Profile } from './profile'; +import { type LegacyUserRaw, parseProfile, type Profile } from './profile'; import { parseMediaGroups, reconstructTweetHtml } from './timeline-tweet-util'; -import { PlaceRaw, Tweet } from './tweets'; +import type { PlaceRaw, Tweet } from './tweets'; import { isFieldDefined } from './type-util'; export interface Hashtag { @@ -382,7 +382,7 @@ function parseTimelineTweet( } } - const views = parseInt(tweet.ext_views?.count ?? ''); + const views = Number.parseInt(tweet.ext_views?.count ?? ''); if (!isNaN(views)) { tw.views = views; } diff --git a/packages/plugin-twitter/src/client/timeline-v2.ts b/packages/plugin-twitter/src/client/timeline-v2.ts index 125a61d8330..e7388e8322e 100644 --- a/packages/plugin-twitter/src/client/timeline-v2.ts +++ b/packages/plugin-twitter/src/client/timeline-v2.ts @@ -1,13 +1,13 @@ -import { LegacyUserRaw } from './profile'; +import type { LegacyUserRaw } from './profile'; import { parseMediaGroups, reconstructTweetHtml } from './timeline-tweet-util'; -import { +import type { LegacyTweetRaw, ParseTweetResult, QueryTweetsResponse, SearchResultRaw, TimelineResultRaw, } from './timeline-v1'; -import { Tweet } from './tweets'; +import type { Tweet } from './tweets'; import { isFieldDefined } from './type-util'; export interface TimelineUserResultRaw { @@ -214,7 +214,7 @@ export function parseLegacyTweet( } } - const views = parseInt(tweet.ext_views?.count ?? ''); + const views = Number.parseInt(tweet.ext_views?.count ?? ''); if (!isNaN(views)) { tw.views = views; } @@ -251,7 +251,7 @@ function parseResult(result?: TimelineResultRaw): ParseTweetResult { } if (!tweetResult.tweet.views && result?.views?.count) { - const views = parseInt(result.views.count); + const views = Number.parseInt(result.views.count); if (!isNaN(views)) { tweetResult.tweet.views = views; } diff --git a/packages/plugin-twitter/src/client/trends.ts b/packages/plugin-twitter/src/client/trends.ts index c4d0e01963e..73b18e91616 100644 --- a/packages/plugin-twitter/src/client/trends.ts +++ b/packages/plugin-twitter/src/client/trends.ts @@ -1,6 +1,6 @@ import { addApiParams, requestApi } from './api'; -import { TwitterAuth } from './auth'; -import { TimelineV1 } from './timeline-v1'; +import type { TwitterAuth } from './auth'; +import type { TimelineV1 } from './timeline-v1'; export async function getTrends(auth: TwitterAuth): Promise { const params = new URLSearchParams(); diff --git a/packages/plugin-twitter/src/client/tweets.test.ts b/packages/plugin-twitter/src/client/tweets.test.ts index 3cf3e96dd17..bf1c87c151e 100644 --- a/packages/plugin-twitter/src/client/tweets.test.ts +++ b/packages/plugin-twitter/src/client/tweets.test.ts @@ -1,6 +1,6 @@ import { getScraper } from './test-utils'; -import { QueryTweetsResponse } from './timeline-v1'; -import { Mention, Tweet, getTweetAnonymous } from './tweets'; +import type { QueryTweetsResponse } from './timeline-v1'; +import { type Mention, type Tweet, getTweetAnonymous } from './tweets'; import fs from 'fs'; import path from 'path'; diff --git a/packages/plugin-twitter/src/client/tweets.ts b/packages/plugin-twitter/src/client/tweets.ts index 15ae877b0c3..1b2741f4d39 100644 --- a/packages/plugin-twitter/src/client/tweets.ts +++ b/packages/plugin-twitter/src/client/tweets.ts @@ -1,22 +1,22 @@ import { addApiFeatures, requestApi } from './api'; -import { TwitterAuth } from './auth'; +import type { TwitterAuth } from './auth'; import { getUserIdByScreenName } from './profile'; -import { QueryTweetsResponse } from './timeline-v1'; +import type { QueryTweetsResponse } from './timeline-v1'; import { parseTimelineTweetsV2, - TimelineV2, - TimelineEntryItemContentRaw, + type TimelineV2, + type TimelineEntryItemContentRaw, parseTimelineEntryItemContentRaw, - ThreadedConversation, + type ThreadedConversation, parseThreadedConversation, parseArticle, - TimelineArticle, + type TimelineArticle, } from './timeline-v2'; import { getTweetTimeline } from './timeline-async'; import { apiRequestFactory } from './api-data'; -import { ListTimeline, parseListTimelineTweets } from './timeline-list'; +import { type ListTimeline, parseListTimelineTweets } from './timeline-list'; import { updateCookieJar } from './requests'; -import { +import type { ApiV2Includes, MediaObjectV2, PlaceV2, diff --git a/packages/plugin-twitter/src/client/type-util.ts b/packages/plugin-twitter/src/client/type-util.ts index f987a4c0350..2955962d960 100644 --- a/packages/plugin-twitter/src/client/type-util.ts +++ b/packages/plugin-twitter/src/client/type-util.ts @@ -3,9 +3,7 @@ export type NonNullableField = { } & T; export function isFieldDefined(key: K) { - return function (value: T): value is NonNullableField { - return isDefined(value[key]); - }; + return (value: T): value is NonNullableField => isDefined(value[key]); } export function isDefined(value: T | null | undefined): value is T { diff --git a/packages/plugin-twitter/src/post.ts b/packages/plugin-twitter/src/post.ts index ea4d98e5532..23a24ddc576 100644 --- a/packages/plugin-twitter/src/post.ts +++ b/packages/plugin-twitter/src/post.ts @@ -14,7 +14,7 @@ import { import type { ClientBase } from "./base.ts"; import type { Tweet } from "./client/index.ts"; import { DEFAULT_MAX_TWEET_LENGTH } from "./environment.ts"; -import { MediaData } from "./types.ts"; +import type { MediaData } from "./types.ts"; import { fetchMediaData } from "./utils.ts"; const twitterPostTemplate = ` diff --git a/packages/plugin-twitter/src/spaces.ts b/packages/plugin-twitter/src/spaces.ts index d978369f5f8..f77dbbbda0a 100644 --- a/packages/plugin-twitter/src/spaces.ts +++ b/packages/plugin-twitter/src/spaces.ts @@ -5,7 +5,7 @@ import { generateText, ModelClass, type TwitterSpaceDecisionOptions, - State, + type State, } from "@elizaos/core"; import type { ClientBase } from "./base.ts"; import { diff --git a/packages/plugin-twitter/src/utils.ts b/packages/plugin-twitter/src/utils.ts index d35f5ba4457..df05318f44d 100644 --- a/packages/plugin-twitter/src/utils.ts +++ b/packages/plugin-twitter/src/utils.ts @@ -6,7 +6,7 @@ import { logger } from "@elizaos/core"; import type { Media } from "@elizaos/core"; import fs from "fs"; import path from "path"; -import { ActionResponse, MediaData } from "./types"; +import type { ActionResponse, MediaData } from "./types"; export const wait = (minTime = 1000, maxTime = 3000) => { const waitTime = diff --git a/turbo.json b/turbo.json index 35140336b1f..d425a58939b 100644 --- a/turbo.json +++ b/turbo.json @@ -16,6 +16,9 @@ "test": { "persistent": true, "dependsOn": ["build"] + }, + "lint:fix": { + "dependsOn": ["build"] } } } \ No newline at end of file