feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
# @coze-arch/web-context
> Global Context for whole hot studio web app. You should keep using this package instead of call `window.xxx` directly
A centralized context management package that provides global state, event handling, and navigation utilities for Coze Studio web applications. This package helps you avoid direct window object manipulation and provides a structured way to handle global application state.
## Features
- **Global Variables Storage**: Type-safe proxy-based global variable management
- **Event Bus System**: Centralized event handling with buffering capabilities
- **Navigation Utilities**: Safe URL redirection with future validation support
- **Application Constants**: Predefined enums for app sections and error codes
- **Community Helpers**: Specialized utilities for community bot store scenarios
## Get Started
### Installation
```bash
# Add to package.json
"@coze-arch/web-context": "workspace:*"
# Then run
rush update
```
### Basic Usage
```typescript
import {
globalVars,
GlobalEventBus,
redirect,
BaseEnum,
SpaceAppEnum,
COZE_TOKEN_INSUFFICIENT_ERROR_CODE
} from '@coze-arch/web-context';
// Use global variables
globalVars.LAST_EXECUTE_ID = 'some-execution-id';
console.log(globalVars.LAST_EXECUTE_ID);
// Create and use event bus
const eventBus = GlobalEventBus.create('myEventBus');
eventBus.on('dataChanged', (data) => {
console.log('Data changed:', data);
});
eventBus.emit('dataChanged', { newValue: 'test' });
// Navigate safely
redirect('/new-page');
```
## API Reference
### Global Variables
#### `globalVars`
A proxy-based storage for global variables that provides type-safe access to shared state.
```typescript
import { globalVars } from '@coze-arch/web-context';
// Set a global variable
globalVars.LAST_EXECUTE_ID = 'abc123';
globalVars.MY_CUSTOM_VAR = { key: 'value' };
// Get a global variable
const executeId = globalVars.LAST_EXECUTE_ID; // string
const customVar = globalVars.MY_CUSTOM_VAR; // unknown
// Undefined for unset variables
const unsetVar = globalVars.NONEXISTENT; // undefined
```
**Features:**
- Type-safe access through TypeScript interfaces
- Automatic storage in internal Map
- Returns `undefined` for unset properties
- Support for any serializable value type
### Event Bus
#### `GlobalEventBus.create<T>(key: string)`
Creates or retrieves a singleton event bus instance for the given key.
```typescript
import { GlobalEventBus } from '@coze-arch/web-context';
// Define event types
interface MyEvents {
userLogin: [userId: string];
dataUpdate: [data: { id: string; value: unknown }];
error: [error: Error];
}
// Create event bus
const eventBus = GlobalEventBus.create<MyEvents>('app');
// Subscribe to events
eventBus.on('userLogin', (userId) => {
console.log(`User ${userId} logged in`);
});
// Emit events
eventBus.emit('userLogin', 'user123');
// Unsubscribe
const handler = (data) => console.log(data);
eventBus.on('dataUpdate', handler);
eventBus.off('dataUpdate', handler);
```
#### Event Bus Control Methods
```typescript
// Stop event processing (events will be buffered)
eventBus.stop();
// Events emitted while stopped are buffered
eventBus.emit('userLogin', 'user456'); // Buffered
// Start processing (flushes buffer)
eventBus.start(); // Now 'user456' login event fires
// Clear buffered events
eventBus.clear();
```
**Key Features:**
- Singleton pattern per key
- Event buffering when stopped
- Type-safe event definitions
- Standard EventEmitter3 API
### Navigation
#### `redirect(href: string)`
Safely redirects to a new URL with potential for future validation logic.
```typescript
import { redirect } from '@coze-arch/web-context';
// Redirect to external URL
redirect('https://example.com');
// Redirect to internal route
redirect('/dashboard');
// Redirect with query parameters
redirect('/search?q=test');
```
### Constants
#### Application Enums
```typescript
import { BaseEnum, SpaceAppEnum } from '@coze-arch/web-context';
// Base application sections
console.log(BaseEnum.Home); // 'home'
console.log(BaseEnum.Store); // 'store'
console.log(BaseEnum.Workspace); // 'space'
// Space-specific applications
console.log(SpaceAppEnum.BOT); // 'bot'
console.log(SpaceAppEnum.WORKFLOW); // 'workflow'
console.log(SpaceAppEnum.PLUGIN); // 'plugin'
```
#### Error Codes
```typescript
import { COZE_TOKEN_INSUFFICIENT_ERROR_CODE } from '@coze-arch/web-context';
// Check for token insufficient errors
const isTokenError = COZE_TOKEN_INSUFFICIENT_ERROR_CODE.includes(errorCode);
if (isTokenError) {
// Handle token insufficient error - stop streaming
}
```
#### Community Constants
```typescript
import {
defaultConversationKey,
defaultConversationUniqId
} from '@coze-arch/web-context';
// Use in community bot store scenarios
const conversationId = defaultConversationKey; // -1
const uniqueId = defaultConversationUniqId; // Unique string
```
## Development
### Project Structure
```
src/
├── const/ # Application constants
│ ├── app.ts # Base and space app enums
│ ├── community.ts # Community-specific constants
│ └── custom.ts # Error codes and custom constants
├── event-bus.ts # Global event bus implementation
├── global-var.ts # Global variables storage
├── location.ts # Navigation utilities
└── index.ts # Main exports
```
### Running Tests
```bash
# Run tests
rush test --to @coze-arch/web-context
# Run tests with coverage
rush test:cov --to @coze-arch/web-context
```
### Linting
```bash
# Lint the package
rush lint --to @coze-arch/web-context
```
## Dependencies
### Runtime Dependencies
- **eventemitter3** (^5.0.1): High-performance event emitter for the event bus system
- **lodash-es** (^4.17.21): Utility functions (used for `uniqueId` generation)
### Development Dependencies
- **@coze-arch/eslint-config**: Shared ESLint configuration
- **@coze-arch/ts-config**: Shared TypeScript configuration
- **vitest**: Testing framework with coverage support
## Best Practices
1. **Use typed event buses**: Always define event interfaces for better type safety
2. **Avoid direct window access**: Use this package instead of direct `window.xxx` calls
3. **Manage event subscriptions**: Remember to unsubscribe from events to prevent memory leaks
4. **Use singleton pattern**: Create event buses with meaningful keys and reuse them
5. **Handle buffered events**: Consider using `stop()`/`start()` pattern for controlling event flow
## License
Copyright © ByteDance Ltd. All rights reserved.

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEnum, SpaceAppEnum } from '../../src/const/app';
describe('const/app', () => {
describe('BaseEnum', () => {
test('should have correct values for all enum members', () => {
expect(BaseEnum.Home).toBe('home');
expect(BaseEnum.Explore).toBe('explore');
expect(BaseEnum.Store).toBe('store');
expect(BaseEnum.Model).toBe('model');
expect(BaseEnum.Space).toBe('space');
expect(BaseEnum.Workflow).toBe('work_flow');
expect(BaseEnum.Invite).toBe('invite');
expect(BaseEnum.Token).toBe('token');
expect(BaseEnum.Open).toBe('open');
expect(BaseEnum.PluginMockSet).toBe('plugin_mock_set');
expect(BaseEnum.Search).toBe('search');
expect(BaseEnum.Premium).toBe('premium');
expect(BaseEnum.User).toBe('user');
});
test('should have the expected number of enum values', () => {
const enumValues = Object.values(BaseEnum);
expect(enumValues.length).toBe(14);
});
});
describe('SpaceAppEnum', () => {
test('should have correct values for all enum members', () => {
expect(SpaceAppEnum.BOT).toBe('bot');
expect(SpaceAppEnum.DEVELOP).toBe('develop');
expect(SpaceAppEnum.LIBRARY).toBe('library');
expect(SpaceAppEnum.PLUGIN).toBe('plugin');
expect(SpaceAppEnum.WORKFLOW).toBe('workflow');
expect(SpaceAppEnum.KNOWLEDGE).toBe('knowledge');
expect(SpaceAppEnum.TEAM).toBe('team');
expect(SpaceAppEnum.PERSONAL).toBe('personal');
expect(SpaceAppEnum.WIDGET).toBe('widget');
expect(SpaceAppEnum.EVALUATION).toBe('evaluation');
expect(SpaceAppEnum.SOCIAL_SCENE).toBe('social-scene');
expect(SpaceAppEnum.IMAGEFLOW).toBe('imageflow');
});
test('should have the expected number of enum values', () => {
const enumValues = Object.values(SpaceAppEnum);
expect(enumValues.length).toBe(19);
});
});
});

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { uniqueId } from 'lodash-es';
import {
defaultConversationKey,
defaultConversationUniqId,
} from '../../src/const/community';
// 模拟 lodash-es 的 uniqueId 函数
vi.mock('lodash-es', () => ({
uniqueId: vi.fn().mockReturnValue('mocked-unique-id'),
}));
describe('const/community', () => {
test('defaultConversationKey should be -1', () => {
expect(defaultConversationKey).toBe(-1);
});
test('defaultConversationUniqId should be a unique ID generated by lodash-es', () => {
// 验证 uniqueId 函数被调用
expect(uniqueId).toHaveBeenCalled();
// 验证 defaultConversationUniqId 的值是由模拟的 uniqueId 函数返回的
expect(defaultConversationUniqId).toBe('mocked-unique-id');
});
});

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { COZE_TOKEN_INSUFFICIENT_ERROR_CODE } from '../../src/const/custom';
describe('const/custom', () => {
describe('COZE_TOKEN_INSUFFICIENT_ERROR_CODE', () => {
test('should be an array', () => {
expect(Array.isArray(COZE_TOKEN_INSUFFICIENT_ERROR_CODE)).toBe(true);
});
test('should contain exactly 2 error codes', () => {
expect(COZE_TOKEN_INSUFFICIENT_ERROR_CODE.length).toBe(2);
});
test('should contain the BOT error code', () => {
expect(COZE_TOKEN_INSUFFICIENT_ERROR_CODE).toContain('702082020');
});
test('should contain the WORKFLOW error code', () => {
expect(COZE_TOKEN_INSUFFICIENT_ERROR_CODE).toContain('702095072');
});
// 移除失败的测试用例
// 原因:在 JavaScript 中,即使使用 const 声明数组,其内容仍然是可变的
// 只有数组引用是不可变的,而不是数组内容
});
});

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GlobalEventBus } from '../src/event-bus';
vi.mock('eventemitter3', () => ({
default: class MockEventEmitter {
eventsMap: Map<string, (() => void)[]> = new Map();
emit(e: string) {
const cbs = this.eventsMap.get(e) ?? [];
cbs.forEach(cb => {
cb();
});
}
on(e: string, cb: () => void) {
const cbs = this.eventsMap.get(e) ?? [];
if (!cbs.find(cb)) {
cbs.push(cb);
this.eventsMap.set(e, cbs);
}
}
off(e: string, cb: () => void) {
const cbs = this.eventsMap.get(e) ?? [];
const idx = cbs.findIndex(c => c === cb);
if (idx !== -1) {
cbs.splice(idx, 1);
this.eventsMap.set(e, cbs);
}
}
},
}));
describe('event-bus', () => {
afterEach(() => {
vi.clearAllMocks();
});
test('A single key corresponds to a unique instance', () => {
const testGlobalEventBus1 = GlobalEventBus.create('test');
const testGlobalEventBus2 = GlobalEventBus.create('test');
expect(testGlobalEventBus1).equal(testGlobalEventBus2);
});
test('Should not trigger events if not started or calling `clear`', () => {
const testGlobalEventBus = GlobalEventBus.create('test2');
testGlobalEventBus.stop();
const testCb = vi.fn();
testGlobalEventBus.on('test_event', testCb);
testGlobalEventBus.emit('test_event');
expect(testCb).not.toHaveBeenCalled();
// Clear the buffer
testGlobalEventBus.clear();
testGlobalEventBus.start();
expect(testCb).not.toHaveBeenCalled();
});
test('Should trigger events if started', () => {
const testGlobalEventBus = GlobalEventBus.create('test3');
const testCb1 = vi.fn();
testGlobalEventBus.on('test_event', testCb1);
testGlobalEventBus.emit('test_event');
expect(testCb1).toHaveBeenCalled();
});
test('Should trigger events if started', () => {
const testGlobalEventBus = GlobalEventBus.create('test4');
const testCb1 = vi.fn();
testGlobalEventBus.on('test_event', testCb1);
testGlobalEventBus.off('test_event', testCb1);
testGlobalEventBus.emit('test_event');
expect(testCb1).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { globalVars } from '../src/global-var';
describe('global-var', () => {
test('should be able to set and get a property', () => {
const testValue = 'Hello, World';
// 设置一个属性
globalVars.TEST_PROP = testValue;
// 确保我们能获取到相同的属性
expect(globalVars.TEST_PROP).toBe(testValue);
});
test('should return undefined for unset property', () => {
expect(globalVars.UNSET_PROP).toBeUndefined();
});
test('should allow to overwrite an existing property', () => {
const firstValue = 'First Value';
const secondValue = 'Second Value';
// 先设置一个属性
globalVars.OVERWRITE_PROP = firstValue;
expect(globalVars.OVERWRITE_PROP).toBe(firstValue);
// 再覆盖这个属性
globalVars.OVERWRITE_PROP = secondValue;
expect(globalVars.OVERWRITE_PROP).toBe(secondValue);
});
});

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { redirect as originalRedirect } from '../src/location';
import {
redirect,
GlobalEventBus,
globalVars,
COZE_TOKEN_INSUFFICIENT_ERROR_CODE,
BaseEnum,
SpaceAppEnum,
defaultConversationKey,
defaultConversationUniqId,
} from '../src/index';
import { globalVars as originalGlobalVars } from '../src/global-var';
import { GlobalEventBus as originalGlobalEventBus } from '../src/event-bus';
import { COZE_TOKEN_INSUFFICIENT_ERROR_CODE as originalCOZE_TOKEN_INSUFFICIENT_ERROR_CODE } from '../src/const/custom';
import {
defaultConversationKey as originalDefaultConversationKey,
defaultConversationUniqId as originalDefaultConversationUniqId,
} from '../src/const/community';
import {
BaseEnum as originalBaseEnum,
SpaceAppEnum as originalSpaceAppEnum,
} from '../src/const/app';
describe('index', () => {
test('should export redirect from location', () => {
expect(redirect).toBe(originalRedirect);
});
test('should export GlobalEventBus from event-bus', () => {
expect(GlobalEventBus).toBe(originalGlobalEventBus);
});
test('should export globalVars from global-var', () => {
expect(globalVars).toBe(originalGlobalVars);
});
test('should export COZE_TOKEN_INSUFFICIENT_ERROR_CODE from const/custom', () => {
expect(COZE_TOKEN_INSUFFICIENT_ERROR_CODE).toBe(
originalCOZE_TOKEN_INSUFFICIENT_ERROR_CODE,
);
});
test('should export BaseEnum from const/app', () => {
expect(BaseEnum).toBe(originalBaseEnum);
});
test('should export SpaceAppEnum from const/app', () => {
expect(SpaceAppEnum).toBe(originalSpaceAppEnum);
});
test('should export defaultConversationKey from const/community', () => {
expect(defaultConversationKey).toBe(originalDefaultConversationKey);
});
test('should export defaultConversationUniqId from const/community', () => {
expect(defaultConversationUniqId).toBe(originalDefaultConversationUniqId);
});
});

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { redirect } from '../src/location';
const viHrefSetter = vi.fn();
vi.stubGlobal('location', {
_href: '',
set href(v: string) {
viHrefSetter(v);
(this as any)._href = v;
},
get href() {
return (this as any)._href;
},
});
describe('location', () => {
test('redirect', () => {
redirect('test');
expect(viHrefSetter).toHaveBeenCalledWith('test');
expect(location.href).equal('test');
});
});

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
}
]
}

View File

@@ -0,0 +1,5 @@
{
"codecov": {
"incrementCoverage": 100
}
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});

View File

@@ -0,0 +1,26 @@
{
"name": "@coze-arch/web-context",
"version": "0.0.1",
"description": "Global Context for whole hot studio web app. You should keep using this package instead of call `window.xxx` directly",
"author": "fanwenjie.fe@bytedance.com",
"main": "./src/index.ts",
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "NODE_OPTIONS='--max_old_space_size=2048' NODE_ENV=test vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"eventemitter3": "^5.0.1",
"lodash-es": "^4.17.21"
},
"devDependencies": {
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@vitest/coverage-v8": "~3.0.5",
"sucrase": "^3.32.0",
"tsconfig-paths": "4.1.0",
"vitest": "~3.0.5"
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// extract from apps/bot/src/constant/app.ts
export enum BaseEnum {
Home = 'home', //首页
Explore = 'explore', //探索
Store = 'store', // 商店
Model = 'model', //模型竞技场
Space = 'space', //空间内
Workflow = 'work_flow', //兼容 workflow 编辑页
Invite = 'invite', // 邀请链接
Token = 'token', // token
Open = 'open', // 开放平台
PluginMockSet = 'plugin_mock_set',
Search = 'search', // 搜索
Premium = 'premium', // 订阅服务
User = 'user', // 个人主页
Enterprise = 'enterprise', // 企业管理
}
export enum SpaceAppEnum {
BOT = 'bot',
DOUYIN_BOT = 'douyin-bot',
DEVELOP = 'develop',
LIBRARY = 'library',
MODEL = 'model',
PLUGIN = 'plugin',
OCEAN_PROJECT = 'ocean-project',
WORKFLOW = 'workflow',
KNOWLEDGE = 'knowledge',
TEAM = 'team',
PERSONAL = 'personal',
WIDGET = 'widget',
EVALUATION = 'evaluation',
EVALUATE = 'evaluate',
SOCIAL_SCENE = 'social-scene',
IMAGEFLOW = 'imageflow',
DATABASE = 'database',
PROJECT_IDE = 'project-ide',
PUBLISH = 'publish',
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { uniqueId } from 'lodash-es';
export const defaultConversationKey = -1;
export const defaultConversationUniqId = uniqueId();

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// extract from apps/bot/src/constant/custom.ts
const enum CozeTokenInsufficientErrorCode {
WORKFLOW = '702095072',
BOT = '702082020',
}
/**
* Coze Token不足错误码
* 当出现该错误码的时候,需要额外进行停止拉流操作
*/
export const COZE_TOKEN_INSUFFICIENT_ERROR_CODE = [
CozeTokenInsufficientErrorCode.BOT,
CozeTokenInsufficientErrorCode.WORKFLOW,
];

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import EventEmitter from 'eventemitter3';
interface EventWithData<T extends EventEmitter.ValidEventTypes> {
event: EventEmitter.EventNames<T>;
args: Parameters<EventEmitter.EventListener<T, EventEmitter.EventNames<T>>>;
}
type ValidEventTypes = EventEmitter.ValidEventTypes;
export class GlobalEventBus<T extends ValidEventTypes> {
private eventEmitter = new EventEmitter<T>();
private started = true;
private buffer: EventWithData<T>[] = [];
private static instances = new Map<string, GlobalEventBus<ValidEventTypes>>();
static create<T extends ValidEventTypes>(key: string): GlobalEventBus<T> {
if (GlobalEventBus.instances.has(key)) {
return GlobalEventBus.instances.get(key) as unknown as GlobalEventBus<T>;
}
const instance = new GlobalEventBus<T>();
GlobalEventBus.instances.set(
key,
instance as unknown as GlobalEventBus<ValidEventTypes>,
);
return instance;
}
/**
* 触发事件
* @param event 事件名称
* @param args 参数
*/
emit<P extends EventEmitter.EventNames<T>>(
event: P,
...args: Parameters<EventEmitter.EventListener<T, P>>
) {
if (!this.started) {
this.buffer.push({
event,
args,
});
return;
}
this.eventEmitter.emit(event, ...args);
}
/**
* 订阅事件
* @param event 事件名称
* @param fn 事件回调
*/
on<P extends EventEmitter.EventNames<T>>(
event: P,
fn: EventEmitter.EventListener<T, P>,
) {
this.eventEmitter.on(event, fn);
}
/**
* 取消订阅事件
* @param event 事件名称
* @param fn 事件回调
*/
off<P extends EventEmitter.EventNames<T>>(
event: P,
fn: EventEmitter.EventListener<T, P>,
) {
this.eventEmitter.off(event, fn);
}
/**
* 开启缓存事件订阅器,开启时会将关闭时收到的事件对应的回调按顺序逐一触发
*/
start() {
this.started = true;
for (const { event, args } of this.buffer) {
this.emit(event, ...args);
}
}
/**
* 关闭缓存事件订阅器,在关闭时收到的事件会被缓存并延迟到下次开启时触发
*/
stop() {
this.started = false;
}
/**
* 清除缓存事件订阅器缓存的事件使得在重新开启start时不会触发在关闭stop时收到的事件对应的回调
*/
clear() {
this.buffer = [];
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @typescript-eslint/naming-convention */
// 需要全局共享的值,可以提前到这里注册类型
interface GlobalVars {
/**
* Last Execute ID that extracts from apps/bot/src/store/bot-detail/utils/execute-draft-bot-request-id.ts
*
* 用于 debug 记录对话接口的 log id不需要响应式所以直接存 const 里了
*/
LAST_EXECUTE_ID: string;
[key: string | symbol]: unknown;
}
const createGlobalVarsStorage = () => {
const storage = new Map();
return new Proxy<GlobalVars>(Object.create(null), {
get<T extends keyof GlobalVars>(_: unknown, prop: T): GlobalVars[T] {
if (storage.has(prop)) {
return storage.get(prop as string);
}
// add more logic for dev mode
return undefined;
},
set<T extends keyof GlobalVars>(_: unknown, prop: T, value: GlobalVars[T]) {
storage.set(prop, value);
return true;
},
}) as GlobalVars;
};
/**
* 通用全局变量
*/
export const globalVars = createGlobalVarsStorage();

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { redirect } from './location';
export { GlobalEventBus } from './event-bus';
export { globalVars } from './global-var';
export { COZE_TOKEN_INSUFFICIENT_ERROR_CODE } from './const/custom';
export { BaseEnum, SpaceAppEnum } from './const/app';
// community bot store detail业务场景专用key值
export {
defaultConversationKey,
defaultConversationUniqId,
} from './const/community';

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// The redirect function is designed to redirect the user to a new URL.
// It takes a single argument href which is a string representing the URL.
// Upon invocation, it sets location.href to the provided URL, thereby navigating to the webpage.
// While no validation logic is currently implemented prior to redirection,
// there is the potential for such checks to be included in the future as per your requirements.
export const redirect = (href: string) => {
// 这里后续可以补充一些校验逻辑
location.href = href;
};

View File

@@ -0,0 +1,21 @@
{
"extends": "@coze-arch/ts-config/tsconfig.node.json",
"compilerOptions": {
"jsx": "preserve",
"useUnknownInCatchVariables": false,
"types": ["vitest/globals"],
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
},
"include": ["./src"],
"$schema": "https://json.schemastore.org/tsconfig",
"references": [
{
"path": "../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../config/ts-config/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
],
"exclude": ["**/*"]
}

View File

@@ -0,0 +1,18 @@
{
"extends": "@coze-arch/ts-config/tsconfig.node.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["__tests__", "vitest.config.ts"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"jsx": "preserve",
"rootDir": "./",
"outDir": "./dist",
"useUnknownInCatchVariables": false,
"types": ["vitest/globals"]
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FIXME: Unable to resolve path to module 'vitest/config'
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
mockReset: false,
coverage: {
provider: 'v8',
reporter: ['cobertura', 'text', 'html', 'clover', 'json'],
exclude: ['src/const', '.eslintrc.js', 'src/index.ts'],
include: ['src/**/*.ts', 'src/**/*.tsx'],
},
},
});