@agentscope-ai/agentscope 0.0.2
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/dist/agent/index.d.mts +234 -0
- package/dist/agent/index.d.ts +234 -0
- package/dist/agent/index.js +1412 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/index.mjs +1375 -0
- package/dist/agent/index.mjs.map +1 -0
- package/dist/base-BOx3UzOl.d.mts +41 -0
- package/dist/base-BoIps2RL.d.ts +41 -0
- package/dist/base-C7jwyH4Z.d.mts +52 -0
- package/dist/base-Cwi4bjze.d.ts +127 -0
- package/dist/base-DYlBMCy_.d.mts +127 -0
- package/dist/base-NX-knWOv.d.ts +52 -0
- package/dist/block-VsnHrllL.d.mts +48 -0
- package/dist/block-VsnHrllL.d.ts +48 -0
- package/dist/event/index.d.mts +181 -0
- package/dist/event/index.d.ts +181 -0
- package/dist/event/index.js +58 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/index.mjs +33 -0
- package/dist/event/index.mjs.map +1 -0
- package/dist/formatter/index.d.mts +187 -0
- package/dist/formatter/index.d.ts +187 -0
- package/dist/formatter/index.js +647 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/formatter/index.mjs +616 -0
- package/dist/formatter/index.mjs.map +1 -0
- package/dist/index-BTJDlKvQ.d.mts +195 -0
- package/dist/index-BcatlwXQ.d.ts +195 -0
- package/dist/index-CAxQAkiP.d.mts +21 -0
- package/dist/index-CAxQAkiP.d.ts +21 -0
- package/dist/mcp/index.d.mts +9 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.js +432 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/index.mjs +408 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/message/index.d.mts +10 -0
- package/dist/message/index.d.ts +10 -0
- package/dist/message/index.js +67 -0
- package/dist/message/index.js.map +1 -0
- package/dist/message/index.mjs +37 -0
- package/dist/message/index.mjs.map +1 -0
- package/dist/message-CkN21KaY.d.mts +99 -0
- package/dist/message-CzLeTlua.d.ts +99 -0
- package/dist/model/index.d.mts +377 -0
- package/dist/model/index.d.ts +377 -0
- package/dist/model/index.js +1880 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/index.mjs +1849 -0
- package/dist/model/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +68 -0
- package/dist/storage/index.d.ts +68 -0
- package/dist/storage/index.js +250 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +212 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/tool/index.d.mts +311 -0
- package/dist/tool/index.d.ts +311 -0
- package/dist/tool/index.js +1494 -0
- package/dist/tool/index.js.map +1 -0
- package/dist/tool/index.mjs +1447 -0
- package/dist/tool/index.mjs.map +1 -0
- package/dist/toolkit-CEpulFi0.d.ts +99 -0
- package/dist/toolkit-CGEZSZPa.d.mts +99 -0
- package/jest.config.js +11 -0
- package/package.json +92 -0
- package/src/_utils/common.ts +104 -0
- package/src/_utils/index.ts +1 -0
- package/src/agent/agent-base.ts +0 -0
- package/src/agent/agent.test.ts +1028 -0
- package/src/agent/agent.ts +1032 -0
- package/src/agent/index.ts +2 -0
- package/src/agent/interfaces.ts +23 -0
- package/src/agent/test-compression.ts +72 -0
- package/src/event/index.ts +250 -0
- package/src/formatter/base.ts +133 -0
- package/src/formatter/dashscope-chat-formatter.test.ts +372 -0
- package/src/formatter/dashscope-chat-formatter.ts +163 -0
- package/src/formatter/deepseek-chat-formatter.ts +130 -0
- package/src/formatter/index.ts +5 -0
- package/src/formatter/ollama-chat-formatter.ts +67 -0
- package/src/formatter/openai-chat-formatter.test.ts +263 -0
- package/src/formatter/openai-chat-formatter.ts +301 -0
- package/src/formatter/openai.md +767 -0
- package/src/mcp/base.ts +114 -0
- package/src/mcp/http.test.ts +303 -0
- package/src/mcp/http.ts +224 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/stdio.test.ts +91 -0
- package/src/mcp/stdio.ts +119 -0
- package/src/message/block.ts +60 -0
- package/src/message/enums.ts +4 -0
- package/src/message/index.ts +12 -0
- package/src/message/message.test.ts +80 -0
- package/src/message/message.ts +131 -0
- package/src/model/base.ts +226 -0
- package/src/model/dashscope-model.test.ts +335 -0
- package/src/model/dashscope-model.ts +441 -0
- package/src/model/deepseek-model.test.ts +279 -0
- package/src/model/deepseek-model.ts +401 -0
- package/src/model/index.ts +7 -0
- package/src/model/ollama-model.test.ts +307 -0
- package/src/model/ollama-model.ts +356 -0
- package/src/model/openai-model.ts +327 -0
- package/src/model/response.ts +22 -0
- package/src/model/usage.ts +12 -0
- package/src/storage/base.ts +52 -0
- package/src/storage/file-system.test.ts +587 -0
- package/src/storage/file-system.ts +269 -0
- package/src/storage/index.ts +2 -0
- package/src/tool/base.ts +23 -0
- package/src/tool/bash.test.ts +174 -0
- package/src/tool/bash.ts +152 -0
- package/src/tool/edit.test.ts +83 -0
- package/src/tool/edit.ts +95 -0
- package/src/tool/glob.test.ts +63 -0
- package/src/tool/glob.ts +166 -0
- package/src/tool/grep.test.ts +74 -0
- package/src/tool/grep.ts +256 -0
- package/src/tool/index.ts +10 -0
- package/src/tool/read.test.ts +77 -0
- package/src/tool/read.ts +117 -0
- package/src/tool/response.ts +82 -0
- package/src/tool/task.test.ts +299 -0
- package/src/tool/task.ts +399 -0
- package/src/tool/toolkit.test.ts +636 -0
- package/src/tool/toolkit.ts +601 -0
- package/src/tool/write.test.ts +52 -0
- package/src/tool/write.ts +57 -0
- package/src/type/index.ts +52 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.cjs.json +11 -0
- package/tsconfig.esm.json +10 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +20 -0
- package/typedoc.json +52 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
import { StdioMCPClient } from './stdio';
|
|
5
|
+
import { Toolkit } from '../tool';
|
|
6
|
+
|
|
7
|
+
// Skip these tests on Windows due to npx/npm cache issues with MCP server packages
|
|
8
|
+
const describeUnlessWindows = process.platform === 'win32' ? describe.skip : describe;
|
|
9
|
+
|
|
10
|
+
describeUnlessWindows('StdIOMCPClient', () => {
|
|
11
|
+
const testDir = path.join(__dirname, 'test-data');
|
|
12
|
+
const testFilePath = path.join(testDir, 'test-file.txt');
|
|
13
|
+
const testContent = 'Hello, this is a test file for MCP filesystem server!';
|
|
14
|
+
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
// Create test directory and file
|
|
17
|
+
if (!fs.existsSync(testDir)) {
|
|
18
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
fs.writeFileSync(testFilePath, testContent, 'utf-8');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterAll(() => {
|
|
24
|
+
// Clean up test directory and all its contents
|
|
25
|
+
if (fs.existsSync(testDir)) {
|
|
26
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('Create StdIOMCPClient, list and execute tools', async () => {
|
|
31
|
+
const client = new StdioMCPClient({
|
|
32
|
+
name: 'filesystem',
|
|
33
|
+
command: 'npx',
|
|
34
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', testDir],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await client.connect();
|
|
38
|
+
|
|
39
|
+
const tools = await client.listTools();
|
|
40
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
41
|
+
|
|
42
|
+
const tool = await client.getCallableFunction({ name: 'read_file' });
|
|
43
|
+
const res = await tool.call({ path: testFilePath });
|
|
44
|
+
expect(res.content.length).toBeGreaterThan(0);
|
|
45
|
+
expect(res.content[0].type).toBe('text');
|
|
46
|
+
if (res.content[0].type === 'text') {
|
|
47
|
+
expect(res.content[0].text).toContain(testContent);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await client.close();
|
|
51
|
+
|
|
52
|
+
await client.connect();
|
|
53
|
+
const tools2 = await client.listTools();
|
|
54
|
+
expect(tools2.length).toBeGreaterThan(0);
|
|
55
|
+
await client.close();
|
|
56
|
+
}, 20000);
|
|
57
|
+
|
|
58
|
+
test('Test toolkit works with StdioMCPClient', async () => {
|
|
59
|
+
const client = new StdioMCPClient({
|
|
60
|
+
name: 'filesystem',
|
|
61
|
+
command: 'npx',
|
|
62
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', testDir],
|
|
63
|
+
});
|
|
64
|
+
await client.connect();
|
|
65
|
+
|
|
66
|
+
const toolkit = new Toolkit();
|
|
67
|
+
await toolkit.registerMCPClient({ client: client, enabledTools: ['read_file'] });
|
|
68
|
+
|
|
69
|
+
const schema = toolkit.getJSONSchemas();
|
|
70
|
+
expect(schema.length).toBe(2);
|
|
71
|
+
expect(schema[1].type).toBe('function');
|
|
72
|
+
expect(schema[1].function.name).toBe('read_file');
|
|
73
|
+
expect(schema[1].function.parameters).toBeDefined();
|
|
74
|
+
|
|
75
|
+
const res = toolkit.callToolFunction({
|
|
76
|
+
id: '123',
|
|
77
|
+
name: 'read_file',
|
|
78
|
+
type: 'tool_call',
|
|
79
|
+
input: `{"path": "${testFilePath}"}`,
|
|
80
|
+
});
|
|
81
|
+
for await (const item of res) {
|
|
82
|
+
expect(item.content.length).toBeGreaterThan(0);
|
|
83
|
+
expect(item.content[0].type).toBe('text');
|
|
84
|
+
if (item.content[0].type === 'text') {
|
|
85
|
+
expect(item.content[0].text).toContain(testContent);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await client.close();
|
|
90
|
+
}, 30000);
|
|
91
|
+
});
|
package/src/mcp/stdio.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import {
|
|
3
|
+
StdioClientTransport,
|
|
4
|
+
StdioServerParameters,
|
|
5
|
+
} from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
6
|
+
import { ListToolsRequest, ListToolsResultSchema, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
|
|
8
|
+
import { MCPTool } from './base';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The STDIO MCP client class.
|
|
12
|
+
*/
|
|
13
|
+
export class StdioMCPClient {
|
|
14
|
+
name: string;
|
|
15
|
+
private stdioServerParameters: StdioServerParameters;
|
|
16
|
+
private transport?: StdioClientTransport;
|
|
17
|
+
private client?: Client;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Initialize the StdioMCPClient with the given Stdio parameters.
|
|
21
|
+
*
|
|
22
|
+
* @param root0
|
|
23
|
+
* @param root0.name
|
|
24
|
+
*/
|
|
25
|
+
constructor({
|
|
26
|
+
name,
|
|
27
|
+
...rest
|
|
28
|
+
}: {
|
|
29
|
+
name: string;
|
|
30
|
+
} & StdioServerParameters) {
|
|
31
|
+
this.name = name;
|
|
32
|
+
this.stdioServerParameters = rest;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Connect to the MCP server using Stdio transport. Note the Stdio client will start an
|
|
37
|
+
* MCP server process under the hood.
|
|
38
|
+
*/
|
|
39
|
+
async connect() {
|
|
40
|
+
this.transport = new StdioClientTransport(this.stdioServerParameters);
|
|
41
|
+
this.client = new Client({
|
|
42
|
+
name: this.name,
|
|
43
|
+
version: '1.0.0',
|
|
44
|
+
});
|
|
45
|
+
await this.client.connect(this.transport);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Close the connection and stop the MCP server process.
|
|
50
|
+
*/
|
|
51
|
+
async close() {
|
|
52
|
+
try {
|
|
53
|
+
if (this.client) {
|
|
54
|
+
await this.client.close();
|
|
55
|
+
}
|
|
56
|
+
} finally {
|
|
57
|
+
this.client = undefined;
|
|
58
|
+
this.transport = undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* List all tools available on the MCP server.
|
|
64
|
+
*
|
|
65
|
+
* @returns An array of MCPTool instances representing the tools available on the server.
|
|
66
|
+
*/
|
|
67
|
+
async listTools(): Promise<MCPTool[]> {
|
|
68
|
+
if (!this.client) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Client not initialized, call 'connect()' method first for the MCP client named '${this.name}'`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const toolsRequest: ListToolsRequest = {
|
|
75
|
+
method: 'tools/list',
|
|
76
|
+
params: {},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const toolsResult = await this.client.request(toolsRequest, ListToolsResultSchema);
|
|
80
|
+
if (toolsResult.tools === undefined) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return toolsResult.tools.map((tool: Tool) => {
|
|
85
|
+
return new MCPTool({
|
|
86
|
+
name: tool.name,
|
|
87
|
+
description: tool.description || '',
|
|
88
|
+
inputSchema: tool.inputSchema,
|
|
89
|
+
getClient: async () => this.client!,
|
|
90
|
+
releaseClient: async () => {},
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get a callable function for a specific tool by its name.
|
|
97
|
+
* @param root0
|
|
98
|
+
* @param root0.name
|
|
99
|
+
* @returns An instance of MCPTool that can be called to execute the tool's functionality.
|
|
100
|
+
*/
|
|
101
|
+
async getCallableFunction({ name }: { name: string }) {
|
|
102
|
+
if (!this.client) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Client not initialized, call 'connect()' method first for the MCP client named '${this.name}'`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const tools = await this.listTools();
|
|
109
|
+
const targetTool = tools.find(tool => tool.name === name);
|
|
110
|
+
|
|
111
|
+
if (!targetTool) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Tool '${name}' not found in MCP server '${this.name}'. Available tools: ${tools.map(t => t.name).join(', ')}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return targetTool;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface TextBlock {
|
|
2
|
+
type: 'text';
|
|
3
|
+
text: string;
|
|
4
|
+
id: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ThinkingBlock {
|
|
8
|
+
type: 'thinking';
|
|
9
|
+
thinking: string;
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface HintBlock {
|
|
14
|
+
type: 'hint';
|
|
15
|
+
hint: string;
|
|
16
|
+
id: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ToolCallBlock {
|
|
20
|
+
type: 'tool_call';
|
|
21
|
+
name: string;
|
|
22
|
+
id: string;
|
|
23
|
+
input: string;
|
|
24
|
+
awaitUserConfirmation?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ToolResultBlock {
|
|
28
|
+
type: 'tool_result';
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
output: string | (TextBlock | DataBlock)[];
|
|
32
|
+
state: 'success' | 'error' | 'interrupted' | 'running';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Base64Source {
|
|
36
|
+
type: 'base64';
|
|
37
|
+
data: string;
|
|
38
|
+
mediaType: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface URLSource {
|
|
42
|
+
type: 'url';
|
|
43
|
+
url: string;
|
|
44
|
+
mediaType: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DataBlock {
|
|
48
|
+
type: 'data';
|
|
49
|
+
source: Base64Source | URLSource;
|
|
50
|
+
id: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type ContentBlock =
|
|
55
|
+
| TextBlock
|
|
56
|
+
| ThinkingBlock
|
|
57
|
+
| HintBlock
|
|
58
|
+
| ToolCallBlock
|
|
59
|
+
| ToolResultBlock
|
|
60
|
+
| DataBlock;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { Msg, createMsg, getTextContent, getContentBlocks } from './message';
|
|
2
|
+
export {
|
|
3
|
+
TextBlock,
|
|
4
|
+
ThinkingBlock,
|
|
5
|
+
ToolCallBlock,
|
|
6
|
+
ToolResultBlock,
|
|
7
|
+
ContentBlock,
|
|
8
|
+
Base64Source,
|
|
9
|
+
URLSource,
|
|
10
|
+
DataBlock,
|
|
11
|
+
} from './block';
|
|
12
|
+
export { GenerateReason } from './enums';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createMsg, getContentBlocks, getTextContent } from './message';
|
|
2
|
+
|
|
3
|
+
describe('Message', () => {
|
|
4
|
+
test('create message object', () => {
|
|
5
|
+
const msg = createMsg({
|
|
6
|
+
name: 'user',
|
|
7
|
+
content: [{ id: crypto.randomUUID(), type: 'text', text: 'Hello, world!' }],
|
|
8
|
+
role: 'user',
|
|
9
|
+
});
|
|
10
|
+
expect(msg.name).toBe('user');
|
|
11
|
+
expect(msg.content).toEqual([
|
|
12
|
+
{ id: expect.any(String), type: 'text', text: 'Hello, world!' },
|
|
13
|
+
]);
|
|
14
|
+
expect(msg.role).toBe('user');
|
|
15
|
+
expect(msg.metadata).toEqual({});
|
|
16
|
+
expect(msg.timestamp).toBeDefined();
|
|
17
|
+
expect(msg.id).toBeDefined();
|
|
18
|
+
expect(getTextContent(msg)).toBe('Hello, world!');
|
|
19
|
+
|
|
20
|
+
// getContentBlocks wraps a string content into a single TextBlock
|
|
21
|
+
const blocks = getContentBlocks(msg);
|
|
22
|
+
expect(blocks.length).toBe(1);
|
|
23
|
+
expect(blocks).toStrictEqual([
|
|
24
|
+
{ id: expect.any(String), type: 'text', text: 'Hello, world!' },
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('obtain different content from message', () => {
|
|
29
|
+
const msg = createMsg({
|
|
30
|
+
name: 'user',
|
|
31
|
+
role: 'user',
|
|
32
|
+
content: [
|
|
33
|
+
{ id: crypto.randomUUID(), type: 'text', text: 'Hello' },
|
|
34
|
+
{ id: crypto.randomUUID(), type: 'thinking', thinking: '...' },
|
|
35
|
+
{ id: crypto.randomUUID(), type: 'text', text: 'World' },
|
|
36
|
+
{
|
|
37
|
+
type: 'tool_call',
|
|
38
|
+
id: '1',
|
|
39
|
+
name: 'test',
|
|
40
|
+
input: "{ query: 'What is AI?' }",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'tool_result',
|
|
44
|
+
id: '1',
|
|
45
|
+
name: 'test',
|
|
46
|
+
output: 'Artificial Intelligence',
|
|
47
|
+
state: 'success',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(getTextContent(msg)).toBe('Hello\nWorld');
|
|
53
|
+
|
|
54
|
+
expect(getContentBlocks(msg, 'text')).toStrictEqual([
|
|
55
|
+
{ id: expect.any(String), type: 'text', text: 'Hello' },
|
|
56
|
+
{ id: expect.any(String), type: 'text', text: 'World' },
|
|
57
|
+
]);
|
|
58
|
+
expect(getContentBlocks(msg, 'thinking')).toStrictEqual([
|
|
59
|
+
{ id: expect.any(String), type: 'thinking', thinking: '...' },
|
|
60
|
+
]);
|
|
61
|
+
expect(getContentBlocks(msg, 'tool_call')).toStrictEqual([
|
|
62
|
+
{
|
|
63
|
+
type: 'tool_call',
|
|
64
|
+
id: '1',
|
|
65
|
+
name: 'test',
|
|
66
|
+
input: "{ query: 'What is AI?' }",
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
expect(getContentBlocks(msg, 'tool_result')).toStrictEqual([
|
|
70
|
+
{
|
|
71
|
+
type: 'tool_result',
|
|
72
|
+
id: '1',
|
|
73
|
+
name: 'test',
|
|
74
|
+
output: 'Artificial Intelligence',
|
|
75
|
+
state: 'success',
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
expect(getContentBlocks(msg, 'data')).toStrictEqual([]);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { JSONSerializableObject } from '../type';
|
|
2
|
+
import {
|
|
3
|
+
ContentBlock,
|
|
4
|
+
TextBlock,
|
|
5
|
+
ThinkingBlock,
|
|
6
|
+
ToolResultBlock,
|
|
7
|
+
ToolCallBlock,
|
|
8
|
+
DataBlock,
|
|
9
|
+
} from './block';
|
|
10
|
+
|
|
11
|
+
/** A chat message exchanged between agents or between an agent and a model. */
|
|
12
|
+
export interface Msg {
|
|
13
|
+
/** Unique identifier for the message. */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Display name of the message sender. */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Conversation role of the sender. */
|
|
18
|
+
role: 'user' | 'assistant' | 'system';
|
|
19
|
+
/** Message body. */
|
|
20
|
+
content: ContentBlock[];
|
|
21
|
+
/** Arbitrary key-value metadata attached to the message. */
|
|
22
|
+
metadata: Record<string, JSONSerializableObject>;
|
|
23
|
+
/** ISO-8601 creation timestamp. */
|
|
24
|
+
timestamp: string;
|
|
25
|
+
/** Usage information for the message, such as token counts. */
|
|
26
|
+
usage?: {
|
|
27
|
+
inputTokens: number;
|
|
28
|
+
outputTokens: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a new {@link Msg} object, filling in `id` and `timestamp` when omitted.
|
|
34
|
+
*
|
|
35
|
+
* @param root0
|
|
36
|
+
* @param root0.name
|
|
37
|
+
* @param root0.content
|
|
38
|
+
* @param root0.role
|
|
39
|
+
* @param root0.metadata
|
|
40
|
+
* @param root0.id
|
|
41
|
+
* @param root0.timestamp
|
|
42
|
+
* @param root0.usage
|
|
43
|
+
* @returns A fully-populated {@link Msg} object.
|
|
44
|
+
*/
|
|
45
|
+
export function createMsg({
|
|
46
|
+
name,
|
|
47
|
+
content,
|
|
48
|
+
role,
|
|
49
|
+
metadata = {},
|
|
50
|
+
id = crypto.randomUUID(),
|
|
51
|
+
timestamp = new Date().toISOString(),
|
|
52
|
+
usage,
|
|
53
|
+
}: Omit<Msg, 'id' | 'timestamp' | 'metadata'> &
|
|
54
|
+
Partial<Pick<Msg, 'id' | 'timestamp' | 'metadata'>>): Msg {
|
|
55
|
+
return { id, name, role, content, metadata, timestamp, usage } as Msg;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extract the plain-text content from a message.
|
|
60
|
+
*
|
|
61
|
+
* When `content` is a string it is returned as-is. When it is an array of
|
|
62
|
+
* content blocks, all {@link TextBlock} texts are joined with `separator`.
|
|
63
|
+
*
|
|
64
|
+
* @param msg - The message to read.
|
|
65
|
+
* @param separator - String inserted between consecutive text blocks. Defaults to `'\n'`.
|
|
66
|
+
* @returns The concatenated text, or `null` when no text blocks are present.
|
|
67
|
+
*/
|
|
68
|
+
export function getTextContent(msg: Msg, separator: string = '\n'): string | null {
|
|
69
|
+
const textBlocks = msg.content.filter(block => block.type === 'text');
|
|
70
|
+
if (textBlocks.length === 0) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return textBlocks.map(block => (block as TextBlock).text).join(separator);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Return all content blocks from a message, regardless of type.
|
|
78
|
+
*
|
|
79
|
+
* When `content` is a plain string it is wrapped in a single {@link TextBlock}.
|
|
80
|
+
*
|
|
81
|
+
* @param msg - The message to read.
|
|
82
|
+
* @returns An array of all {@link ContentBlock} objects.
|
|
83
|
+
*/
|
|
84
|
+
export function getContentBlocks(msg: Msg): ContentBlock[];
|
|
85
|
+
/**
|
|
86
|
+
* Return all {@link TextBlock} objects from a message.
|
|
87
|
+
*
|
|
88
|
+
* @param msg - The message to read.
|
|
89
|
+
* @param blockType - `'text'`
|
|
90
|
+
* @returns An array of {@link TextBlock} objects.
|
|
91
|
+
*/
|
|
92
|
+
export function getContentBlocks(msg: Msg, blockType: 'text'): TextBlock[];
|
|
93
|
+
/**
|
|
94
|
+
* Return all {@link ThinkingBlock} objects from a message.
|
|
95
|
+
*
|
|
96
|
+
* @param msg - The message to read.
|
|
97
|
+
* @param blockType - `'thinking'`
|
|
98
|
+
* @returns An array of {@link ThinkingBlock} objects.
|
|
99
|
+
*/
|
|
100
|
+
export function getContentBlocks(msg: Msg, blockType: 'thinking'): ThinkingBlock[];
|
|
101
|
+
/**
|
|
102
|
+
* Return all {@link DataBlock} objects from a message.
|
|
103
|
+
*
|
|
104
|
+
* @param msg - The message to read.
|
|
105
|
+
* @param blockType - `'video'`
|
|
106
|
+
* @returns An array of {@link DataBlock} objects.
|
|
107
|
+
*/
|
|
108
|
+
export function getContentBlocks(msg: Msg, blockType: 'data'): DataBlock[];
|
|
109
|
+
/**
|
|
110
|
+
* Return all {@link ToolCallBlock} objects from a message.
|
|
111
|
+
*
|
|
112
|
+
* @param msg - The message to read.
|
|
113
|
+
* @param blockType - `'tool_call'`
|
|
114
|
+
* @returns An array of {@link ToolCallBlock} objects.
|
|
115
|
+
*/
|
|
116
|
+
export function getContentBlocks(msg: Msg, blockType: 'tool_call'): ToolCallBlock[];
|
|
117
|
+
/**
|
|
118
|
+
* Return all {@link ToolResultBlock} objects from a message.
|
|
119
|
+
*
|
|
120
|
+
* @param msg - The message to read.
|
|
121
|
+
* @param blockType - `'tool_result'`
|
|
122
|
+
* @returns An array of {@link ToolResultBlock} objects.
|
|
123
|
+
*/
|
|
124
|
+
export function getContentBlocks(msg: Msg, blockType: 'tool_result'): ToolResultBlock[];
|
|
125
|
+
export function getContentBlocks(
|
|
126
|
+
msg: Msg,
|
|
127
|
+
blockType?: 'text' | 'thinking' | 'data' | 'tool_call' | 'tool_result'
|
|
128
|
+
): ContentBlock[] {
|
|
129
|
+
if (!blockType) return msg.content;
|
|
130
|
+
return msg.content.filter(block => block.type === blockType);
|
|
131
|
+
}
|