Skip to content

Commit

Permalink
feat!: new utils-{chat,stream} package (#36)
Browse files Browse the repository at this point in the history
* refactor!: new `utils-{chat,stream}` package

* fix: lint code

* feat(utils-stream): add smoothStream

* refactor(utils-stream/smooth-stream): handle zero delay

* feat(utils-stream): add simulateReadableStream

* chore!: relicense to apache-2.0

* chore(utils): remove private field

* fix(license): update copyright

* Revert "fix(license): update copyright"

This reverts commit 7a85cd6.

* Revert "chore!: relicense to apache-2.0"

This reverts commit 826b420.

* chore(utils-stream): add license header

* chore(utils-stream): update license header
  • Loading branch information
kwaa authored Jan 23, 2025
1 parent ef31ccf commit ac14b49
Show file tree
Hide file tree
Showing 14 changed files with 182 additions and 3 deletions.
1 change: 0 additions & 1 deletion packages/shared-chat/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { chat } from './chat'
export * as message from './message'
35 changes: 35 additions & 0 deletions packages/utils-chat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@xsai/utils-chat",
"type": "module",
"version": "0.0.29",
"description": "extra-small AI SDK for Browser, Node.js, Deno, Bun or Edge Runtime.",
"author": "Moeru AI",
"license": "MIT",
"homepage": "https://xsai.js.org",
"repository": {
"type": "git",
"url": "git+https://github.com/moeru-ai/xsai.git",
"directory": "packages/utils-chat"
},
"bugs": "https://github.com/moeru-ai/xsai/issues",
"keywords": ["xsai", "openai", "ai"],
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "pkgroll",
"build:watch": "pkgroll --watch"
},
"dependencies": {
"@xsai/shared-chat": "workspace:"
}
}
1 change: 1 addition & 0 deletions packages/utils-chat/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as message from './message'
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { AssistantMessage, AssistantMessagePart, Message, SystemMessage, SystemMessagePart, ToolCall, ToolMessage, ToolMessagePart, UserMessage, UserMessagePart } from '../types/message'
import type { ImagePart, TextPart } from '../types/message-part'
import type { AssistantMessage, AssistantMessagePart, ImagePart, Message, SystemMessage, SystemMessagePart, TextPart, ToolCall, ToolMessage, ToolMessagePart, UserMessage, UserMessagePart } from '@xsai/shared-chat'

export const messages = (...messages: Message[]): Message[] => messages

Expand Down
4 changes: 4 additions & 0 deletions packages/utils-chat/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "@importantimport/tsconfig/app.json",
"include": ["src"]
}
32 changes: 32 additions & 0 deletions packages/utils-stream/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@xsai/utils-stream",
"type": "module",
"version": "0.0.29",
"description": "extra-small AI SDK for Browser, Node.js, Deno, Bun or Edge Runtime.",
"author": "Moeru AI",
"license": "MIT",
"homepage": "https://xsai.js.org",
"repository": {
"type": "git",
"url": "git+https://github.com/moeru-ai/xsai.git",
"directory": "packages/utils-stream"
},
"bugs": "https://github.com/moeru-ai/xsai/issues",
"keywords": ["xsai", "openai", "ai"],
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "pkgroll",
"build:watch": "pkgroll --watch"
}
}
4 changes: 4 additions & 0 deletions packages/utils-stream/src/_sleep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const sleep = async (delay: number) => delay === 0
? Promise.resolve()
// eslint-disable-next-line @masknet/no-timer, @masknet/prefer-timer-id
: new Promise(resolve => setTimeout(resolve, delay))
3 changes: 3 additions & 0 deletions packages/utils-stream/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { simulateReadableStream } from './simulate-readable-stream'
export { smoothStream } from './smooth-stream'
export { toAsyncIterator } from './to-async-iterator'
24 changes: 24 additions & 0 deletions packages/utils-stream/src/simulate-readable-stream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// License for this File only:
//
// Copyright Vercel, Inc. (https://vercel.com)
// Copyright Moeru AI (https://github.com/moeru-ai)
// SPDX-License-Identifier: Apache-2.0

import { sleep } from './_sleep'

export interface SimulateReadableStreamOptions<T> {
chunkDelay: number
chunks: T[]
initialDelay: number
}

/** @experimental */
export const simulateReadableStream = <T>({ chunkDelay, chunks, initialDelay }: SimulateReadableStreamOptions<T>) => new ReadableStream({
pull: async (controller) => {
for (const [index, chunk] of chunks.entries()) {
await sleep(index === 0 ? initialDelay : chunkDelay)
controller.enqueue(chunk)
}
controller.close()
},
})
47 changes: 47 additions & 0 deletions packages/utils-stream/src/smooth-stream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// License for this File only:
//
// Copyright Vercel, Inc. (https://vercel.com)
// Copyright Moeru AI (https://github.com/moeru-ai)
// SPDX-License-Identifier: Apache-2.0

import { sleep } from './_sleep'

export interface SmoothStreamOptions {
chunking?: 'line' | 'word' | RegExp
delay?: number
}

const CHUNKING_REGEXPS = {
// eslint-disable-next-line sonarjs/slow-regex
line: /[^\n]*\n/,
// eslint-disable-next-line sonarjs/slow-regex
word: /\s*\S+\s+/,
}

/**
* @experimental
* Smooths text streaming output.
*/
export const smoothStream = ({ chunking = 'word', delay = 10 }: SmoothStreamOptions = {}): TransformStream<string, string> => {
const chunkingRegexp
= typeof chunking === 'string' ? CHUNKING_REGEXPS[chunking] : chunking

let buffer = ''

return new TransformStream<string, string>({
transform: async (chunk, controller) => {
buffer += chunk

let match = chunkingRegexp.exec(buffer)
while (match !== null) {
const result = match[0]
controller.enqueue(result)
buffer = buffer.slice(result.length)

match = chunkingRegexp.exec(buffer)

await sleep(delay)
}
},
})
}
18 changes: 18 additions & 0 deletions packages/utils-stream/src/to-async-iterator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export async function* toAsyncIterator<T>(
stream: ReadableStream<T>,
): AsyncGenerator<T, void, unknown> {
const reader = stream.getReader()
try {
while (true) {
const { done, value } = await reader.read()

if (done)
return

yield value
}
}
finally {
reader.releaseLock()
}
}
4 changes: 4 additions & 0 deletions packages/utils-stream/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "@importantimport/tsconfig/app.json",
"include": ["src"]
}
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
{ "path": "./packages/stream-object/tsconfig.json" },
{ "path": "./packages/stream-text/tsconfig.json" },
{ "path": "./packages/tool/tsconfig.json" },
{ "path": "./packages/utils-stream/tsconfig.json" },
{ "path": "./packages/xsai/tsconfig.json" },
{ "path": "./tsconfig.node.json" }
],
Expand Down

0 comments on commit ac14b49

Please sign in to comment.