From c13811b39299158fab1e8a3574342548f25813a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=97=8D+85CD?= <50108258+kwaa@users.noreply.github.com> Date: Wed, 25 Jun 2025 15:31:30 +0800 Subject: [PATCH] refactor: use `@moeru/std` (#226) --- apps/realtime-audio/package.json | 1 - apps/stage-tamagotchi/package.json | 1 - apps/stage-web/package.json | 1 - packages/server-runtime/package.json | 1 - packages/server-sdk/src/client.ts | 23 +++++------ packages/stage-ui/package.json | 1 - packages/stage-ui/src/composables/micvad.ts | 8 ++-- packages/stage-ui/src/composables/whisper.ts | 6 +-- packages/stage-ui/src/stores/chat.ts | 4 +- packages/stage-ui/src/utils/index.ts | 1 - packages/stage-ui/src/utils/iterator.ts | 18 --------- pnpm-lock.yaml | 23 ++--------- pnpm-workspace.yaml | 30 +++++++------- services/minecraft/src/agents/chat/llm.ts | 13 +++---- services/minecraft/src/libs/llm-agent/chat.ts | 12 +++--- .../minecraft/src/libs/llm-agent/handler.ts | 12 +++--- .../minecraft/src/libs/llm-agent/voice.ts | 10 +++-- services/minecraft/src/utils/helper.ts | 39 ------------------- services/twitter-services/package.json | 2 +- services/twitter-services/src/config/index.ts | 6 +-- 20 files changed, 67 insertions(+), 145 deletions(-) delete mode 100644 packages/stage-ui/src/utils/iterator.ts delete mode 100644 services/minecraft/src/utils/helper.ts diff --git a/apps/realtime-audio/package.json b/apps/realtime-audio/package.json index 6d60e8e5b..0265990ae 100644 --- a/apps/realtime-audio/package.json +++ b/apps/realtime-audio/package.json @@ -25,7 +25,6 @@ "@xsai/generate-text": "catalog:", "@xsai/shared-chat": "catalog:", "@xsai/stream-text": "catalog:", - "defu": "^6.1.4", "es-toolkit": "^1.39.4", "vue": "^3.5.17" }, diff --git a/apps/stage-tamagotchi/package.json b/apps/stage-tamagotchi/package.json index be43af8ad..8f15c193e 100644 --- a/apps/stage-tamagotchi/package.json +++ b/apps/stage-tamagotchi/package.json @@ -45,7 +45,6 @@ "@xsai/stream-text": "catalog:", "@xsai/utils-chat": "catalog:", "culori": "^4.0.1", - "defu": "^6.1.4", "drizzle-kit": "^0.31.2", "drizzle-orm": "^0.44.2", "jszip": "^3.10.1", diff --git a/apps/stage-web/package.json b/apps/stage-web/package.json index 4ea14aed0..fb23a1e70 100644 --- a/apps/stage-web/package.json +++ b/apps/stage-web/package.json @@ -50,7 +50,6 @@ "@xsai/stream-text": "catalog:", "@xsai/utils-chat": "catalog:", "culori": "^4.0.1", - "defu": "^6.1.4", "driver.js": "^1.3.6", "drizzle-kit": "^0.31.2", "drizzle-orm": "^0.44.2", diff --git a/packages/server-runtime/package.json b/packages/server-runtime/package.json index ada04fd99..1ac68405f 100644 --- a/packages/server-runtime/package.json +++ b/packages/server-runtime/package.json @@ -38,7 +38,6 @@ "@guiiai/logg": "^1.0.10", "@proj-airi/server-shared": "workspace:^", "crossws": "^0.4.1", - "defu": "^6.1.4", "h3": "^1.15.3", "listhen": "^1.9.0" } diff --git a/packages/server-sdk/src/client.ts b/packages/server-sdk/src/client.ts index 93ee58dbe..61cd070f3 100644 --- a/packages/server-sdk/src/client.ts +++ b/packages/server-sdk/src/client.ts @@ -2,7 +2,6 @@ import type { WebSocketBaseEvent, WebSocketEvent, WebSocketEvents } from '@proj- import { sleep } from '@moeru/std' import WebSocket from 'crossws/websocket' -import { defu } from 'defu' export interface ClientOptions { url?: string @@ -17,7 +16,7 @@ export interface ClientOptions { export class Client { private connected = false - private opts: Required> + private opts: Required, 'token'>> & Pick, 'token'> private websocket: WebSocket | undefined private eventListeners: Map, Array<(data: WebSocketBaseEvent) => void | Promise>> = new Map() @@ -25,17 +24,15 @@ export class Client { private shouldClose = false constructor(options: ClientOptions) { - this.opts = defu>, Required, 'name' | 'token'>>[]>( - options, - { - url: 'ws://localhost:6121/ws', - possibleEvents: [], - onError: () => { }, - onClose: () => { }, - autoConnect: true, - autoReconnect: true, - }, - ) + this.opts = { + url: 'ws://localhost:6121/ws', + possibleEvents: [], + onError: () => { }, + onClose: () => { }, + autoConnect: true, + autoReconnect: true, + ...options, + } if (this.opts.autoConnect) { try { diff --git a/packages/stage-ui/package.json b/packages/stage-ui/package.json index baa594ef3..2d2bc7043 100644 --- a/packages/stage-ui/package.json +++ b/packages/stage-ui/package.json @@ -78,7 +78,6 @@ "@xsai/stream-text": "catalog:", "@xsai/tool": "catalog:", "culori": "^4.0.1", - "defu": "^6.1.4", "gpuu": "^1.0.4", "jszip": "^3.10.1", "localforage": "^1.10.0", diff --git a/packages/stage-ui/src/composables/micvad.ts b/packages/stage-ui/src/composables/micvad.ts index 1692ca939..b91ec7665 100644 --- a/packages/stage-ui/src/composables/micvad.ts +++ b/packages/stage-ui/src/composables/micvad.ts @@ -1,21 +1,21 @@ import type { RealTimeVADOptions } from '@ricky0123/vad-web' import type { MaybeRef } from '@vueuse/shared' +import { merge } from '@moeru/std' import { getDefaultRealTimeVADOptions, MicVAD } from '@ricky0123/vad-web' import { usePermission } from '@vueuse/core' import { tryOnMounted } from '@vueuse/shared' -import { defu } from 'defu' import { onUnmounted, ref, toRef, unref, watch } from 'vue' -export function useMicVAD(deviceId: MaybeRef, options?: Partial & { auto?: boolean }) { - const opts = defu & { auto?: boolean }, Array & { auto?: boolean }>>(options ?? {}, { +export function useMicVAD(deviceId: MaybeRef, options: Partial & { auto?: boolean } = {}) { + const opts = merge & { auto?: boolean }, Partial & { auto?: boolean }>({ ...getDefaultRealTimeVADOptions('v5'), preSpeechPadFrames: 30, positiveSpeechThreshold: 0.5, // default is 0.5 negativeSpeechThreshold: 0.5 - 0.15, // default is 0.5 - 0.15 minSpeechFrames: 30, // default is 9 auto: true, - }) + }, options) const micVad = ref() const microphoneAccess = usePermission('microphone') diff --git a/packages/stage-ui/src/composables/whisper.ts b/packages/stage-ui/src/composables/whisper.ts index 3224aacec..afc2f72ca 100644 --- a/packages/stage-ui/src/composables/whisper.ts +++ b/packages/stage-ui/src/composables/whisper.ts @@ -1,7 +1,7 @@ import type { MessageEvents, MessageGenerate, ProgressMessageEvents } from '../libs/workers/types' +import { merge } from '@moeru/std' import { useWebWorker } from '@vueuse/core' -import { defu } from 'defu' import { onUnmounted, ref, watch } from 'vue' export interface UseWhisperOptions { @@ -16,7 +16,7 @@ export interface UseWhisperOptions { } export function useWhisper(url: string, options?: Partial) { - const opts = defu, UseWhisperOptions[]>(options, { + const opts = merge({ onLoading: () => {}, onInitiate: () => {}, onProgress: () => {}, @@ -25,7 +25,7 @@ export function useWhisper(url: string, options?: Partial) { onStart: () => {}, onUpdate: () => {}, onComplete: () => {}, - }) + }, options) const { post: whisperPost, diff --git a/packages/stage-ui/src/stores/chat.ts b/packages/stage-ui/src/stores/chat.ts index 91dae5fc1..fb4a6759f 100644 --- a/packages/stage-ui/src/stores/chat.ts +++ b/packages/stage-ui/src/stores/chat.ts @@ -3,13 +3,13 @@ import type { Message, SystemMessage } from '@xsai/shared-chat' import type { ChatAssistantMessage, ChatMessage, ChatSlices } from '../types/chat' +import { readableStreamToAsyncIterator } from '@moeru/std' import { defineStore, storeToRefs } from 'pinia' import { ref, toRaw } from 'vue' import { useQueue } from '../composables' import { useLlmmarkerParser } from '../composables/llmmarkerParser' import { useLLM } from '../stores/llm' -import { asyncIteratorFromReadableStream } from '../utils' import { useAiriCardStore } from './modules' export interface ErrorMessage { @@ -175,7 +175,7 @@ export const useChatStore = defineStore('chat', () => { let fullText = '' - for await (const textPart of asyncIteratorFromReadableStream(res.textStream, async v => v)) { + for await (const textPart of readableStreamToAsyncIterator(res.textStream, async v => v)) { slicesQueue.add({ type: 'text', text: textPart, diff --git a/packages/stage-ui/src/utils/index.ts b/packages/stage-ui/src/utils/index.ts index 063596c33..903ac9ee2 100644 --- a/packages/stage-ui/src/utils/index.ts +++ b/packages/stage-ui/src/utils/index.ts @@ -1,2 +1 @@ export * from './eye-motions' -export * from './iterator' diff --git a/packages/stage-ui/src/utils/iterator.ts b/packages/stage-ui/src/utils/iterator.ts deleted file mode 100644 index b75159970..000000000 --- a/packages/stage-ui/src/utils/iterator.ts +++ /dev/null @@ -1,18 +0,0 @@ -export async function* asyncIteratorFromReadableStream(res: ReadableStream, func: (value: F) => Promise): AsyncGenerator { - // react js - TS2504: Type 'ReadableStream' must have a '[Symbol.asyncIterator]()' method that returns an async iterator - Stack Overflow - // https://stackoverflow.com/questions/76700924/ts2504-type-readablestreamuint8array-must-have-a-symbol-asynciterator - const reader = res.getReader() - try { - while (true) { - const { done, value } = await reader.read() - if (done) { - return - } - - yield func(value) - } - } - finally { - reader.releaseLock() - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ba56fca4..30392da46 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,7 +7,7 @@ settings: catalogs: default: '@moeru/std': - specifier: ^0.1.0-beta.4 + specifier: 0.1.0-beta.4 version: 0.1.0-beta.4 '@proj-airi/drizzle-duckdb-wasm': specifier: ^0.4.28 @@ -230,9 +230,6 @@ importers: '@xsai/stream-text': specifier: 'catalog:' version: 0.3.0-beta.5 - defu: - specifier: ^6.1.4 - version: 6.1.4 es-toolkit: specifier: ^1.39.4 version: 1.39.4 @@ -354,9 +351,6 @@ importers: culori: specifier: ^4.0.1 version: 4.0.1 - defu: - specifier: ^6.1.4 - version: 6.1.4 drizzle-kit: specifier: ^0.31.2 version: 0.31.2 @@ -643,9 +637,6 @@ importers: culori: specifier: ^4.0.1 version: 4.0.1 - defu: - specifier: ^6.1.4 - version: 6.1.4 driver.js: specifier: ^1.3.6 version: 1.3.6 @@ -897,9 +888,6 @@ importers: crossws: specifier: ^0.4.1 version: 0.4.1 - defu: - specifier: ^6.1.4 - version: 6.1.4 h3: specifier: ^1.15.3 version: 1.15.3 @@ -1056,9 +1044,6 @@ importers: culori: specifier: ^4.0.1 version: 4.0.1 - defu: - specifier: ^6.1.4 - version: 6.1.4 gpuu: specifier: ^1.0.4 version: 1.0.4 @@ -1492,12 +1477,12 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.13.1 version: 1.13.1 + '@moeru/std': + specifier: 'catalog:' + version: 0.1.0-beta.4 '@proj-airi/server-sdk': specifier: ^0.6.1 version: 0.6.1 - defu: - specifier: ^6.1.4 - version: 6.1.4 dotenv: specifier: ^16.5.0 version: 16.5.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b9aa59bdd..2bd1c9d1c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,22 +7,22 @@ packages: - '!**/dist/**' catalog: - '@moeru/std': ^0.1.0-beta.4 + '@moeru/std': 0.1.0-beta.4 '@proj-airi/drizzle-duckdb-wasm': ^0.4.28 - '@xsai-ext/providers-cloud': &xsai ^0.3.0-beta.5 - '@xsai-ext/providers-local': *xsai - '@xsai-ext/shared-providers': *xsai - '@xsai/embed': *xsai - '@xsai/generate-speech': *xsai - '@xsai/generate-text': *xsai - '@xsai/generate-transcription': *xsai - '@xsai/model': *xsai - '@xsai/shared': *xsai - '@xsai/shared-chat': *xsai - '@xsai/stream-text': *xsai - '@xsai/tool': *xsai - '@xsai/utils-chat': *xsai - xsschema: *xsai + '@xsai-ext/providers-cloud': ^0.3.0-beta.5 + '@xsai-ext/providers-local': ^0.3.0-beta.5 + '@xsai-ext/shared-providers': ^0.3.0-beta.5 + '@xsai/embed': ^0.3.0-beta.5 + '@xsai/generate-speech': ^0.3.0-beta.5 + '@xsai/generate-text': ^0.3.0-beta.5 + '@xsai/generate-transcription': ^0.3.0-beta.5 + '@xsai/model': ^0.3.0-beta.5 + '@xsai/shared': ^0.3.0-beta.5 + '@xsai/shared-chat': ^0.3.0-beta.5 + '@xsai/stream-text': ^0.3.0-beta.5 + '@xsai/tool': ^0.3.0-beta.5 + '@xsai/utils-chat': ^0.3.0-beta.5 + xsschema: ^0.3.0-beta.5 catalogs: rolldown-vite: diff --git a/services/minecraft/src/agents/chat/llm.ts b/services/minecraft/src/agents/chat/llm.ts index 29c73bbec..cd317284f 100644 --- a/services/minecraft/src/agents/chat/llm.ts +++ b/services/minecraft/src/agents/chat/llm.ts @@ -2,11 +2,11 @@ import type { Agent, Neuri } from 'neuri' import type { ChatHistory } from './types' +import { withRetry } from '@moeru/std' import { agent } from 'neuri' import { system, user } from 'neuri/openai' import { config as appConfig } from '../../composables/config' -import { toRetriable } from '../../utils/helper' import { useLogger } from '../../utils/logger' import { generateChatAgentPrompt } from './adapter' @@ -56,13 +56,12 @@ export async function generateChatResponse( return content } - const retriableHandler = toRetriable( - config.retryLimit ?? 3, - config.delayInterval ?? 1000, - handleCompletion, - ) + const retryHandler = withRetry(handleCompletion, { + retry: config.retryLimit ?? 3, + retryDelay: config.delayInterval ?? 1000, + }) - return await retriableHandler(c) + return await retryHandler(c) }) if (!content) { diff --git a/services/minecraft/src/libs/llm-agent/chat.ts b/services/minecraft/src/libs/llm-agent/chat.ts index ff6658920..63adf49b3 100644 --- a/services/minecraft/src/libs/llm-agent/chat.ts +++ b/services/minecraft/src/libs/llm-agent/chat.ts @@ -3,9 +3,9 @@ import type { Neuri, NeuriContext } from 'neuri' import type { Logger } from '../../utils/logger' import type { MineflayerWithAgents } from './types' +import { withRetry } from '@moeru/std' import { system, user } from 'neuri/openai' -import { toRetriable } from '../../utils/helper' import { handleLLMCompletion } from './completion' import { generateStatusPrompt } from './prompt' @@ -29,11 +29,13 @@ export async function handleChatMessage(username: string, message: string, bot: [...bot.memory.chatHistory, system(statusPrompt)], async (c: NeuriContext) => { logger.log('handling response...') - return toRetriable( - 3, - 1000, + return withRetry( ctx => handleLLMCompletion(ctx, bot, logger), - { onError: err => logger.withError(err).log('error occurred') }, + { + retry: 3, + retryDelay: 1000, + onError: err => logger.withError(err).log('error occurred'), + }, )(c) }, ) diff --git a/services/minecraft/src/libs/llm-agent/handler.ts b/services/minecraft/src/libs/llm-agent/handler.ts index a8620afd4..f2871963b 100644 --- a/services/minecraft/src/libs/llm-agent/handler.ts +++ b/services/minecraft/src/libs/llm-agent/handler.ts @@ -4,8 +4,9 @@ import type { ChatCompletion, Message } from 'neuri/openai' import type { Logger } from '../../utils/logger' import type { LLMConfig, LLMResponse } from './types' +import { withRetry } from '@moeru/std' + import { config } from '../../composables/config' -import { toRetriable } from '../../utils/helper' import { useLogger } from '../../utils/logger' export abstract class BaseLLMHandler { @@ -39,10 +40,9 @@ export abstract class BaseLLMHandler { } protected createRetryHandler(handler: (context: NeuriContext) => Promise) { - return toRetriable( - this.config.retryLimit ?? 3, - this.config.delayInterval ?? 1000, - handler, - ) + return withRetry(handler, { + retry: this.config.retryLimit ?? 3, + retryDelay: this.config.delayInterval ?? 1000, + }) } } diff --git a/services/minecraft/src/libs/llm-agent/voice.ts b/services/minecraft/src/libs/llm-agent/voice.ts index 9fbe69ee2..4597be634 100644 --- a/services/minecraft/src/libs/llm-agent/voice.ts +++ b/services/minecraft/src/libs/llm-agent/voice.ts @@ -3,9 +3,9 @@ import type { Neuri, NeuriContext } from 'neuri' import type { Logger } from '../../utils/logger' import type { MineflayerWithAgents } from './types' +import { withRetry } from '@moeru/std' import { system, user } from 'neuri/openai' -import { toRetriable } from '../../utils/helper' import { handleLLMCompletion } from './completion' import { generateStatusPrompt } from './prompt' @@ -29,10 +29,12 @@ export async function handleVoiceInput(event: any, bot: MineflayerWithAgents, ag logger.log('Plan executed successfully') // Generate response - const retryHandler = toRetriable( - 3, - 1000, + const retryHandler = withRetry( ctx => handleLLMCompletion(ctx, bot, logger), + { + retry: 3, + retryDelay: 1000, + }, ) const content = await agent.handleStateless( diff --git a/services/minecraft/src/utils/helper.ts b/services/minecraft/src/utils/helper.ts deleted file mode 100644 index e434db33e..000000000 --- a/services/minecraft/src/utils/helper.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { sleep } from '@moeru/std' - -/** - * Returns a retirable anonymous function with configured retryLimit and delayInterval - * - * @param retryLimit Number of retry attempts - * @param delayInterval Delay between retries in milliseconds - * @param func Function to be called - * @returns A wrapped function with the same signature as func - */ -export function toRetriable( - retryLimit: number, - delayInterval: number, - func: (...args: A[]) => Promise, - hooks?: { - onError?: (err: unknown) => void - }, -): (...args: A[]) => Promise { - let retryCount = 0 - return async function (args: A): Promise { - try { - return await func(args) - } - catch (err) { - if (hooks?.onError) { - hooks.onError(err) - } - - if (retryCount < retryLimit) { - retryCount++ - await sleep(delayInterval) - return await toRetriable(retryLimit - retryCount, delayInterval, func)(args) - } - else { - throw err - } - } - } -} diff --git a/services/twitter-services/package.json b/services/twitter-services/package.json index bab561bea..9c839aa91 100644 --- a/services/twitter-services/package.json +++ b/services/twitter-services/package.json @@ -15,8 +15,8 @@ "@browserbasehq/stagehand": "^2.3.1", "@guiiai/logg": "^1.0.10", "@modelcontextprotocol/sdk": "^1.13.1", + "@moeru/std": "catalog:", "@proj-airi/server-sdk": "^0.6.1", - "defu": "^6.1.4", "dotenv": "^16.5.0", "h3": "^1.15.3", "listhen": "^1.9.0", diff --git a/services/twitter-services/src/config/index.ts b/services/twitter-services/src/config/index.ts index 6a072140b..b71bd14d4 100644 --- a/services/twitter-services/src/config/index.ts +++ b/services/twitter-services/src/config/index.ts @@ -4,7 +4,7 @@ import fs from 'node:fs' import path from 'node:path' import process from 'node:process' -import { defu } from 'defu' +import { merge } from '@moeru/std' import { config as configDotenv } from 'dotenv' import { logger } from '../utils/logger' @@ -72,7 +72,7 @@ export class ConfigManager { // Use defu to deeply merge configurations // Values in fileConfig take precedence over this.config - this.config = defu(fileConfig, this.config) + this.config = merge(this.config, fileConfig) logger.config.log(`Configuration loaded from ${filePath}`) } @@ -93,7 +93,7 @@ export class ConfigManager { */ updateConfig(newConfig: Partial): void { // Use defu to merge new configuration - this.config = defu(newConfig, this.config) + this.config = merge(this.config, newConfig) } }