@cloudbase/agent-ui-miniprogram 1.0.1-alpha.7 → 1.0.1-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @cloudbase/agent-ui-miniprogram
2
2
 
3
+ ## 1.0.1-alpha.8
4
+
5
+ ### Patch Changes
6
+
7
+ - alpha release 0.1.2-alpha.1
8
+ - Update all public packages to version 0.1.2-alpha.1
9
+ - Trigger automated alpha release workflow
10
+ - Includes latest features and improvements
11
+
12
+ ## 1.0.1-alpha.7
13
+
14
+ ### Patch Changes
15
+
16
+ - alpha release 0.1.2-alpha.1
17
+ - Update all public packages to version 0.1.2-alpha.1
18
+ - Trigger automated alpha release workflow
19
+ - Includes latest features and improvements
20
+
3
21
  ## 1.0.1-alpha.6
4
22
 
5
23
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,466 @@
1
+ # @cloudbase/agent-ui-miniprogram
2
+
3
+ 微信小程序的 [AG-UI](https://docs.ag-ui.com) 客户端 SDK。
4
+
5
+ ## 为什么用这个库?
6
+
7
+ **[AG-UI](https://docs.ag-ui.com)** 是一个用于构建 AI 对话应用的协议。它定义了客户端和服务端如何通信——流式文本、工具调用、消息历史等。
8
+
9
+ 如果从零开始构建 AG-UI 客户端,你需要:
10
+
11
+ 1. 解析服务端的 Server-Sent Events 流
12
+ 2. 处理 15+ 种事件类型(TEXT_MESSAGE_START、TOOL_CALL_ARGS、RUN_ERROR...)
13
+ 3. 维护消息状态、合并流式片段、跟踪工具调用状态
14
+ 4. 执行客户端工具并回传结果
15
+ 5. 保持 UI 与所有状态变化同步
16
+
17
+ **@cloudbase/agent-ui-miniprogram 帮你处理了这些逻辑。** 你只需要:
18
+
19
+ 调用 `this.agui.sendMessage` 发送消息:
20
+
21
+ ```javascript
22
+ // chat.js
23
+ this.agui.sendMessage('你好')
24
+ ```
25
+
26
+ 将注入的消息列表数据 `uiMessages` 渲染成 UI:
27
+
28
+ ```xml
29
+ <!-- chat.wxml -->
30
+ <view wx:for="{{agui.uiMessages}}" wx:key="id">
31
+ {{item.role}}: {{item.parts[0].text}}
32
+ </view>
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 安装
38
+
39
+ ```bash
40
+ npm install @cloudbase/agent-ui-miniprogram@beta
41
+ ```
42
+
43
+ ---
44
+
45
+ ## 快速开始
46
+
47
+ ```javascript
48
+ import { createAGUIBehavior, CloudbaseTransport } from '@cloudbase/agent-ui-miniprogram'
49
+
50
+ Component({
51
+ behaviors: [
52
+ createAGUIBehavior({
53
+ transport: new CloudbaseTransport({ botId: 'your-bot-id' }),
54
+ })
55
+ ],
56
+
57
+ methods: {
58
+ onSend(e) {
59
+ this.agui.sendMessage(e.detail.value)
60
+ }
61
+ }
62
+ })
63
+ ```
64
+
65
+ ```xml
66
+ <view class="chat">
67
+ <view wx:for="{{agui.uiMessages}}" wx:key="id" class="message {{item.role}}">
68
+ <block wx:for="{{item.parts}}" wx:for-item="part" wx:key="id">
69
+ <text wx:if="{{part.type === 'text'}}">{{part.text}}</text>
70
+ <view wx:if="{{part.type === 'tool'}}" class="tool">
71
+ 🔧 {{part.name}}: {{part.status}}
72
+ </view>
73
+ </block>
74
+ </view>
75
+ <view wx:if="{{agui.error}}" class="error">{{agui.error.message}}</view>
76
+ </view>
77
+
78
+ <input placeholder="输入消息..." bindconfirm="onSend" disabled="{{agui.isRunning}}" />
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 核心概念
84
+
85
+ 本 SDK 使用小程序的 [Behavior](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html) 机制——一种可以在多个组件间复用代码的 mixin 模式。
86
+
87
+ 当你在组件中添加由 `createAGUIBehavior()` 创建出的 Behavior 后,它会:
88
+
89
+ 1. 将 AG-UI 相关状态注入到 `this.data.agui` 中
90
+ 2. 提供 `this.agui.xxx` 方法调用 AG-UI 相关能力
91
+
92
+ ### 状态
93
+
94
+ Behavior 会在 `this.data.agui` 中提供以下数据:
95
+
96
+ | 属性 | 用途 |
97
+ |------|------|
98
+ | `uiMessages` | 渲染聊天界面 |
99
+ | `isRunning` | 显示加载状态、禁用输入框 |
100
+ | `error` | 显示错误信息 |
101
+
102
+ 对于其他数据,请查看 API Reference 了解。
103
+
104
+ ### Transport
105
+
106
+ ```mermaid
107
+ flowchart LR
108
+ A[小程序<br/>@cloudbase/agent-ui-miniprogram] <-->|transport| B[AG-UI 服务端<br/>你的后端]
109
+ B <--> C[LLM<br/>Claude 等]
110
+ ```
111
+
112
+ @cloudbase/agent-ui-miniprogram 采用了通信层(Transport)无关的设计。它只负责处理 AG-UI 事件流、管理状态、提供交互方法,而不关心如何与后端通信。
113
+
114
+ Transport 层专门负责与后端通信,你可以在创建 Behavior 的时候传入任意符合定义的 Transport 实例。
115
+
116
+ ```javascript
117
+ createAGUIBehavior({
118
+ transport: new AnyTransport()
119
+ })
120
+ ```
121
+
122
+ @cloudbase/agent-ui-miniprogram 提供了 `CloudbaseTransport`,对接 Cloudbase 云开发 Agent。
123
+
124
+ ```javascript
125
+ createAGUIBehavior({
126
+ transport: new CloudbaseTransport({ botId: '...' })
127
+ })
128
+ ```
129
+
130
+ 自定义 Transport 需要满足的接口:
131
+
132
+ ```typescript
133
+ interface Transport {
134
+ run(input: RunAgentInput): AsyncIterable<BaseEvent>
135
+ }
136
+ ```
137
+
138
+ ### 客户端工具
139
+
140
+ 客户端工具能够让 Agent 能够与小程序环境交互。
141
+
142
+ **使用场景:**
143
+
144
+ - 访问设备功能(位置、相机、存储)
145
+ - 读取客户端数据(用户偏好、购物车)
146
+ - 触发 UI 操作(导航、弹窗、提示)
147
+
148
+ **示例:**
149
+
150
+ ```javascript
151
+ createAGUIBehavior({
152
+ transport,
153
+ tools: [{
154
+ name: 'get_location',
155
+ description: '获取用户当前位置',
156
+ parameters: { type: 'object', properties: {} },
157
+ handler: async ({ args }) => {
158
+ const res = await wx.getLocation()
159
+ return JSON.stringify(res)
160
+ }
161
+ }]
162
+ })
163
+ ```
164
+
165
+ 当 Agent 决定调用工具时,SDK 会执行你的 handler 并自动将结果发回给 Agent。
166
+
167
+ ---
168
+
169
+ ## CloudbaseTransport 配置
170
+
171
+ 用于微信云开发 Cloudbase 的 Transport 实现,基于 `wx.cloud.extend.AI.bot`。
172
+
173
+ ### 前置条件
174
+
175
+ 1. **开通微信云开发** - 在小程序中启用云开发
176
+ 2. **创建 Agent** - 创建一个 AI Bot 并获取其 `agentId`
177
+
178
+ ### 配置步骤
179
+
180
+ #### 1. 在 app.js 中初始化云开发
181
+
182
+ ```javascript
183
+ // app.js
184
+ App({
185
+ onLaunch() {
186
+ wx.cloud.init({
187
+ env: 'your-env-id'
188
+ })
189
+ }
190
+ })
191
+ ```
192
+
193
+ #### 2. 使用 CloudbaseTransport
194
+
195
+ ```javascript
196
+ import { createAGUIBehavior, CloudbaseTransport } from '@cloudbase/agent-ui-miniprogram'
197
+
198
+ const transport = new CloudbaseTransport({
199
+ botId: 'your-agent-id' // 从云开发控制台获取
200
+ })
201
+
202
+ Component({
203
+ behaviors: [createAGUIBehavior({ transport })]
204
+ })
205
+ ```
206
+
207
+ ### CloudbaseTransport 构造函数
208
+
209
+ ```typescript
210
+ new CloudbaseTransport(options: { botId: string })
211
+ ```
212
+
213
+ | 选项 | 类型 | 必需 | 说明 |
214
+ |------|------|------|------|
215
+ | `botId` | `string` | 是 | 云开发控制台中的 Agent ID |
216
+
217
+ ### 工作原理
218
+
219
+ CloudbaseTransport 内部调用 `wx.cloud.extend.AI.bot.sendMessage()`:
220
+
221
+ ```javascript
222
+ const res = await wx.cloud.extend.AI.bot.sendMessage({
223
+ botId: 'your-bot-id',
224
+ data: {
225
+ threadId: '...',
226
+ messages: [...],
227
+ tools: [...],
228
+ context: [...]
229
+ }
230
+ })
231
+
232
+ for await (const event of res.eventStream) {
233
+ // AG-UI 事件在这里流式返回
234
+ }
235
+ ```
236
+
237
+ ---
238
+
239
+ ## API 参考
240
+
241
+ ### `createAGUIBehavior(options?)`
242
+
243
+ 创建一个带有 AG-UI 状态管理的 Behavior mixin。
244
+
245
+ ```javascript
246
+ import { createAGUIBehavior } from '@cloudbase/agent-ui-miniprogram'
247
+
248
+ Component({
249
+ behaviors: [createAGUIBehavior(options)]
250
+ })
251
+ ```
252
+
253
+ #### 配置项
254
+
255
+ | 选项 | 类型 | 说明 |
256
+ |------|------|------|
257
+ | `transport` | `Transport` | Transport 实现(`sendMessage` 必需) |
258
+ | `threadId` | `string` | 会话线程 ID(不传则自动生成) |
259
+ | `messages` | `Message[]` | 初始消息 |
260
+ | `tools` | `ClientTool[]` | 带有 handler 的客户端工具 |
261
+ | `contexts` | `Context[]` | Agent 运行时的上下文 |
262
+ | `onRawEvent` | `(event: BaseEvent) => void` | 每个原始 AG-UI 事件的回调 |
263
+
264
+ ### 实例方法
265
+
266
+ 组件 attached 后通过 `this.agui.xxx` 访问。
267
+
268
+ | 方法 | 签名 | 说明 |
269
+ |------|------|------|
270
+ | `init` | `(config: AGUIConfig) => void` | 初始化或重新初始化 transport/threadId |
271
+ | `sendMessage` | `(input: string \| Message[]) => Promise<void>` | 发送消息并运行 agent |
272
+ | `appendMessage` | `(message: Message) => void` | 添加消息但不触发 agent |
273
+ | `setMessages` | `(messages: Message[]) => void` | 替换整个消息历史 |
274
+ | `reset` | `() => void` | 重置为配置项中的初始状态 |
275
+ | `setThreadId` | `(threadId: string) => void` | 更改线程 ID |
276
+ | `addTool` | `(tool: ClientTool) => void` | 注册新工具 |
277
+ | `removeTool` | `(name: string) => void` | 按名称移除工具 |
278
+ | `updateTool` | `(name: string, updates: Partial<ClientTool>) => void` | 更新工具属性 |
279
+ | `clearTools` | `() => void` | 移除所有工具 |
280
+
281
+ ### 状态属性
282
+
283
+ 通过 `this.data.agui.xxx` 访问(WXML 中使用 `{{agui.xxx}}`)。
284
+
285
+ | 属性 | 类型 | 说明 |
286
+ |------|------|------|
287
+ | `messages` | `Message[]` | 原始消息历史(用于 AI) |
288
+ | `uiMessages` | `UIMessage[]` | UI 优化的消息(用于渲染) |
289
+ | `isRunning` | `boolean` | Agent 是否正在处理中 |
290
+ | `error` | `AGUIClientError \| null` | 当前错误(如果有) |
291
+ | `activeToolCalls` | `ToolCallState[]` | 进行中的工具调用 |
292
+ | `threadId` | `string` | 当前会话线程 ID |
293
+ | `tools` | `Tool[]` | 已注册的工具定义(不含 handler) |
294
+ | `contexts` | `Context[]` | 已注册的上下文 |
295
+ | `runId` | `string \| null` | 当前运行 ID(未运行时为 null) |
296
+
297
+ ### `this.agui` 完整类型
298
+
299
+ 可以通过 `this.agui` 访问到所有的数据和方法。
300
+
301
+ ```typescript
302
+ interface AGUINamespace {
303
+ // 方法
304
+ init(config: AGUIConfig): void
305
+ sendMessage(input: string | Message[]): Promise<void>
306
+ appendMessage(message: Message): void
307
+ setMessages(messages: Message[]): void
308
+ reset(): void
309
+ setThreadId(threadId: string): void
310
+ addTool(tool: ClientTool): void
311
+ removeTool(name: string): void
312
+ updateTool(name: string, updates: Partial<ClientTool>): void
313
+ clearTools(): void
314
+
315
+ // 状态(只读,与 this.data.agui 同步)
316
+ readonly messages: Message[]
317
+ readonly uiMessages: UIMessage[]
318
+ readonly isRunning: boolean
319
+ readonly error: AGUIClientError | null
320
+ readonly activeToolCalls: ToolCallState[]
321
+ readonly threadId: string
322
+ readonly tools: Tool[]
323
+ readonly contexts: Context[]
324
+ readonly runId: string | null
325
+
326
+ // 配置(只读,传入 createAGUIBehavior 的原始选项)
327
+ readonly config: CreateAGUIBehaviorOptions
328
+ }
329
+ ```
330
+
331
+ ### 类型定义
332
+
333
+ #### AG-UI 协议类型
334
+
335
+ 以下类型来自 [AG-UI 协议](https://docs.ag-ui.com),本 SDK 对标版本:**v0.0.42**
336
+
337
+ 详细定义请参考 AG-UI 官方文档:
338
+ - [`Message`](https://docs.ag-ui.com/sdk/js/core/types#message) - 消息结构
339
+ - [`Tool`](https://docs.ag-ui.com/sdk/js/core/types#tool) - 工具定义
340
+ - [`ToolCall`](https://docs.ag-ui.com/sdk/js/core/types#toolcall) - 工具调用结构
341
+ - [`Context`](https://docs.ag-ui.com/sdk/js/core/types#context) - Agent 上下文
342
+ - [`RunAgentInput`](https://docs.ag-ui.com/sdk/js/core/types#runagentinput) - Agent 运行输入
343
+ - [`BaseEvent`](https://docs.ag-ui.com/sdk/js/core/events) - 事件基础结构
344
+ - [`EventType`](https://docs.ag-ui.com/sdk/js/core/events) - 事件类型枚举
345
+
346
+ #### UIMessage
347
+
348
+ ```typescript
349
+ interface UIMessage {
350
+ id: string
351
+ role: 'user' | 'assistant' | 'system' | 'developer'
352
+ parts: UIMessagePart[]
353
+ }
354
+
355
+ type UIMessagePart = TextPart | ToolPart
356
+
357
+ interface TextPart {
358
+ id: string
359
+ type: 'text'
360
+ text: string
361
+ state?: 'streaming' | 'done'
362
+ }
363
+
364
+ interface ToolPart {
365
+ id: string
366
+ type: 'tool'
367
+ toolCallId: string
368
+ name: string
369
+ argsString: string
370
+ args: Record<string, unknown>
371
+ status: 'pending' | 'ready' | 'executing' | 'completed' | 'failed'
372
+ result?: unknown
373
+ error?: AGUIClientError
374
+ }
375
+ ```
376
+
377
+ #### ClientTool
378
+
379
+ 扩展自 AG-UI 的 `Tool` 类型,增加了 `handler` 函数用于执行客户端工具。
380
+
381
+ ```typescript
382
+ interface ClientTool extends Tool {
383
+ handler: (params: ToolHandlerParams) => string | Promise<string>
384
+ }
385
+
386
+ interface ToolHandlerParams {
387
+ name: string
388
+ description: string
389
+ toolCallId: string
390
+ args: Record<string, unknown>
391
+ }
392
+ ```
393
+
394
+ #### AGUIClientError
395
+
396
+ ```typescript
397
+ interface AGUIClientError {
398
+ code: AGUIClientErrorCode
399
+ message: string
400
+ recoverable: boolean
401
+ details?: unknown
402
+ originalError?: Error
403
+ }
404
+
405
+ type AGUIClientErrorCode =
406
+ | 'INIT_ERROR'
407
+ | 'TRANSPORT_ERROR'
408
+ | 'RUNTIME_ERROR'
409
+ | 'INVALID_EVENT'
410
+ | 'TOOL_EXECUTION_ERROR'
411
+ | 'TOOL_NOT_FOUND'
412
+ | 'PARSE_ERROR'
413
+ | 'TIMEOUT'
414
+ | 'INVALID_CONFIG'
415
+ | 'UNKNOWN'
416
+ ```
417
+
418
+ #### ToolCallState
419
+
420
+ ```typescript
421
+ interface ToolCallState {
422
+ toolCallId: string
423
+ name: string
424
+ argsString: string // 原始参数字符串(流式传输中)
425
+ args: Record<string, unknown> // 解析后的参数
426
+ status: ToolCallStatus
427
+ result?: unknown
428
+ error?: AGUIClientError
429
+ }
430
+
431
+ type ToolCallStatus = 'pending' | 'ready' | 'executing' | 'completed' | 'failed'
432
+ ```
433
+
434
+ #### AGUIConfig
435
+
436
+ ```typescript
437
+ interface AGUIConfig {
438
+ transport: Transport
439
+ threadId?: string
440
+ }
441
+ ```
442
+
443
+ #### CreateAGUIBehaviorOptions
444
+
445
+ ```typescript
446
+ interface CreateAGUIBehaviorOptions {
447
+ transport?: Transport
448
+ threadId?: string
449
+ messages?: Message[]
450
+ tools?: ClientTool[]
451
+ contexts?: Context[]
452
+ onRawEvent?: (event: BaseEvent) => void
453
+ }
454
+ ```
455
+
456
+ #### Transport
457
+
458
+ ```typescript
459
+ interface Transport {
460
+ run(input: RunAgentInput): AsyncIterable<BaseEvent>
461
+ }
462
+ ```
463
+
464
+ > `RunAgentInput` 和 `BaseEvent` 类型定义见 [AG-UI 协议类型](#ag-ui-协议类型)。
465
+
466
+ ---
@@ -0,0 +1,65 @@
1
+ /**
2
+ * AG-UI Behavior for WeChat Miniprogram
3
+ * Main entry point - provides the Behavior mixin pattern
4
+ */
5
+ import { type CreateAGUIBehaviorOptions } from "./types";
6
+ /**
7
+ * Creates an AG-UI Behavior mixin for WeChat Mini Program components.
8
+ *
9
+ * This factory function returns a Behavior definition that provides AG-UI
10
+ * protocol support via the `agui` namespace on component data.
11
+ *
12
+ * @param options - Configuration options for the behavior
13
+ * @param options.transport - Transport instance for communicating with the agent backend
14
+ * @param options.messages - Initial message history to populate the conversation
15
+ * @param options.tools - Client tools that the agent can invoke
16
+ * @param options.threadId - Custom thread ID (auto-generated if not provided)
17
+ * @param options.contexts - Additional context objects to pass to the agent
18
+ * @param options.onRawEvent - Callback invoked for each raw AG-UI event received
19
+ * @returns A Behavior definition to be mixed into a Component
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * // Basic usage with transport
24
+ * const transport = new CloudbaseTransport({ botId: 'your-bot-id' });
25
+ * Component({
26
+ * behaviors: [createAGUIBehavior({ transport })],
27
+ * });
28
+ *
29
+ * // With initial messages and tools
30
+ * Component({
31
+ * behaviors: [createAGUIBehavior({
32
+ * transport,
33
+ * messages: [{ id: 'm1', role: 'user', content: 'Hello' }],
34
+ * tools: [{
35
+ * name: 'get_weather',
36
+ * description: 'Get current weather',
37
+ * parameters: { type: 'object', properties: { city: { type: 'string' } } },
38
+ * handler: async ({ args }) => ({ temp: 72, city: args.city }),
39
+ * }],
40
+ * })],
41
+ * });
42
+ * ```
43
+ */
44
+ export declare function createAGUIBehavior(options?: CreateAGUIBehaviorOptions): string;
45
+ /**
46
+ * Default AG-UI behavior instance with no static configuration.
47
+ *
48
+ * Use this for a purely imperative usage pattern where all configuration
49
+ * is done at runtime via `agui.init()`.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * Component({
54
+ * behaviors: [aguiBehavior],
55
+ * lifetimes: {
56
+ * attached() {
57
+ * this.agui.init({
58
+ * transport: new CloudbaseTransport({ botId: 'my-bot' }),
59
+ * });
60
+ * },
61
+ * },
62
+ * });
63
+ * ```
64
+ */
65
+ export declare const aguiBehavior: string;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * AG-UI Event Handler
3
+ * Processes AG-UI events and updates state accordingly
4
+ */
5
+ import { type BaseEvent, type Message, type AGUIState, type PendingToolCall, type ActiveMessage, type UIMessage } from "./types";
6
+ /**
7
+ * AG-UI event type - uses local BaseEvent to avoid Zod dependency
8
+ */
9
+ export type AGUIEvent = BaseEvent;
10
+ /**
11
+ * Context for event handling - provides access to current state and update functions
12
+ */
13
+ export interface EventHandlerContext {
14
+ getState(): AGUIState;
15
+ setData(updates: Record<string, unknown>): void;
16
+ /** Callback uses BaseEvent for public API simplicity */
17
+ onRawEvent?: (event: BaseEvent) => void;
18
+ }
19
+ /**
20
+ * Internal state tracked during event processing
21
+ */
22
+ export interface EventProcessingState {
23
+ activeMessages: Map<string, ActiveMessage>;
24
+ pendingToolCalls: Map<string, PendingToolCall>;
25
+ /** Maps raw message id to uiMessage id (for merged messages) */
26
+ rawToUiMessageMap: Map<string, string>;
27
+ }
28
+ /**
29
+ * Create initial processing state
30
+ */
31
+ export declare function createProcessingState(): EventProcessingState;
32
+ /**
33
+ * Rebuild UI-optimized messages from raw AG-UI messages.
34
+ *
35
+ * This utility function transforms the raw message array into a format
36
+ * optimized for UI rendering:
37
+ * - Filters out tool role messages (they appear as parts in assistant messages)
38
+ * - Merges consecutive assistant messages into a single UIMessage with multiple parts
39
+ * - Converts text content and tool calls into a unified parts-based structure
40
+ *
41
+ * @param messages - Raw AG-UI message array
42
+ * @returns Array of UIMessage objects optimized for rendering
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const messages: Message[] = [
47
+ * { id: 'm1', role: 'user', content: 'Hello' },
48
+ * { id: 'm2', role: 'assistant', content: 'Hi there!' },
49
+ * { id: 'm3', role: 'assistant', content: 'How can I help?' },
50
+ * ];
51
+ *
52
+ * const uiMessages = rebuildUiMessages(messages);
53
+ * // Result: [
54
+ * // { id: 'm1', role: 'user', parts: [{ type: 'text', text: 'Hello' }] },
55
+ * // { id: 'm2', role: 'assistant', parts: [
56
+ * // { type: 'text', text: 'Hi there!' },
57
+ * // { type: 'text', text: 'How can I help?' },
58
+ * // ] },
59
+ * // ]
60
+ * ```
61
+ */
62
+ export declare function rebuildUiMessages(messages: Message[]): UIMessage[];
63
+ /**
64
+ * Process a single AG-UI event and update state
65
+ */
66
+ export declare function processEvent(event: AGUIEvent, ctx: EventHandlerContext, processingState: EventProcessingState): void;