mu-core 0.15.0 → 0.16.3
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/esm/agent.d.ts +39 -0
- package/esm/agent.js +96 -0
- package/esm/index.d.ts +4 -0
- package/esm/index.js +2 -0
- package/esm/package.json +3 -0
- package/esm/types.d.ts +59 -0
- package/esm/types.js +3 -0
- package/package.json +14 -10
- package/script/agent.d.ts +39 -0
- package/script/agent.js +101 -0
- package/script/index.d.ts +4 -0
- package/script/index.js +10 -0
- package/script/package.json +3 -0
- package/script/types.d.ts +59 -0
- package/script/types.js +9 -0
- package/README.md +0 -110
- package/src/activity.test.ts +0 -44
- package/src/activity.ts +0 -83
- package/src/agent.test.ts +0 -91
- package/src/agent.ts +0 -249
- package/src/channel.test.ts +0 -52
- package/src/channel.ts +0 -77
- package/src/hooks.test.ts +0 -105
- package/src/hooks.ts +0 -112
- package/src/host/index.ts +0 -135
- package/src/host/startMu.test.ts +0 -66
- package/src/index.ts +0 -74
- package/src/plugin.ts +0 -389
- package/src/provider/adapter.ts +0 -100
- package/src/provider/registry.test.ts +0 -37
- package/src/provider/registry.ts +0 -26
- package/src/provider/transport.test.ts +0 -58
- package/src/provider/transport.ts +0 -103
- package/src/registry.context.test.ts +0 -71
- package/src/registry.ts +0 -484
- package/src/session.test.ts +0 -99
- package/src/session.ts +0 -248
- package/src/types/llm.ts +0 -120
- package/src/ui.ts +0 -49
package/esm/agent.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ContentPart, Message, Provider, Tool, Usage } from './types.js';
|
|
2
|
+
export type LoopEvent = ContentPart | {
|
|
3
|
+
type: 'usage';
|
|
4
|
+
usage: Usage;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'reasoning';
|
|
7
|
+
text: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'message';
|
|
10
|
+
message: Message;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'done';
|
|
13
|
+
messages: Message[];
|
|
14
|
+
};
|
|
15
|
+
export interface RunOptions {
|
|
16
|
+
provider: Provider;
|
|
17
|
+
model: string;
|
|
18
|
+
messages: Message[];
|
|
19
|
+
tools?: Tool[];
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
}
|
|
22
|
+
export declare function run(opts: RunOptions): AsyncIterable<LoopEvent>;
|
|
23
|
+
export interface AgentConfig {
|
|
24
|
+
provider: Provider;
|
|
25
|
+
model: string;
|
|
26
|
+
tools?: Tool[];
|
|
27
|
+
system?: string;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
export type Input = string | ContentPart[] | Message[];
|
|
31
|
+
export interface AgentResult {
|
|
32
|
+
message: Message;
|
|
33
|
+
messages: Message[];
|
|
34
|
+
}
|
|
35
|
+
export interface Agent {
|
|
36
|
+
stream(input: Input): AsyncIterable<LoopEvent>;
|
|
37
|
+
run(input: Input): Promise<AgentResult>;
|
|
38
|
+
}
|
|
39
|
+
export declare const createAgent: (config: AgentConfig) => Agent;
|
package/esm/agent.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const append = (parts, part) => {
|
|
2
|
+
const last = parts[parts.length - 1];
|
|
3
|
+
if (part.type === 'text' && last?.type === 'text') {
|
|
4
|
+
last.text += part.text;
|
|
5
|
+
}
|
|
6
|
+
else if (part.type === 'audio' && last?.type === 'audio' && last.mime === part.mime) {
|
|
7
|
+
const merged = new Uint8Array(last.data.length + part.data.length);
|
|
8
|
+
merged.set(last.data);
|
|
9
|
+
merged.set(part.data, last.data.length);
|
|
10
|
+
last.data = merged;
|
|
11
|
+
}
|
|
12
|
+
else if (part.type === 'text') {
|
|
13
|
+
parts.push({ type: 'text', text: part.text });
|
|
14
|
+
}
|
|
15
|
+
else if (part.type === 'audio') {
|
|
16
|
+
parts.push({ type: 'audio', mime: part.mime, data: part.data });
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
parts.push(part);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const execute = async (tools, call, signal) => {
|
|
23
|
+
const tool = tools.get(call.name);
|
|
24
|
+
if (!tool)
|
|
25
|
+
return [{ type: 'text', text: `Unknown tool: ${call.name}` }];
|
|
26
|
+
try {
|
|
27
|
+
return await tool.run(call.input, { signal });
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
return [{ type: 'text', text: err instanceof Error ? err.message : String(err) }];
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
export async function* run(opts) {
|
|
34
|
+
const { provider, model, signal } = opts;
|
|
35
|
+
const tools = opts.tools ?? [];
|
|
36
|
+
const registry = new Map(tools.map((t) => [t.name, t]));
|
|
37
|
+
const messages = [...opts.messages];
|
|
38
|
+
while (true) {
|
|
39
|
+
const content = [];
|
|
40
|
+
const calls = [];
|
|
41
|
+
for await (const event of provider.stream({ model, messages, tools, signal })) {
|
|
42
|
+
if (event.type === 'usage' || event.type === 'reasoning') {
|
|
43
|
+
yield event;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
yield event;
|
|
47
|
+
append(content, event);
|
|
48
|
+
if (event.type === 'tool_call')
|
|
49
|
+
calls.push(event);
|
|
50
|
+
}
|
|
51
|
+
const message = { role: 'assistant', content };
|
|
52
|
+
messages.push(message);
|
|
53
|
+
yield { type: 'message', message };
|
|
54
|
+
if (calls.length === 0)
|
|
55
|
+
break;
|
|
56
|
+
const results = await Promise.all(calls.map(async (call) => ({
|
|
57
|
+
type: 'tool_result',
|
|
58
|
+
id: call.id,
|
|
59
|
+
content: await execute(registry, call, signal),
|
|
60
|
+
})));
|
|
61
|
+
const toolMessage = { role: 'user', content: results };
|
|
62
|
+
messages.push(toolMessage);
|
|
63
|
+
yield { type: 'message', message: toolMessage };
|
|
64
|
+
}
|
|
65
|
+
yield { type: 'done', messages };
|
|
66
|
+
}
|
|
67
|
+
const isMessages = (input) => input.length > 0 && 'role' in input[0];
|
|
68
|
+
const toMessages = (input) => {
|
|
69
|
+
if (typeof input === 'string')
|
|
70
|
+
return [{ role: 'user', content: [{ type: 'text', text: input }] }];
|
|
71
|
+
if (isMessages(input))
|
|
72
|
+
return input;
|
|
73
|
+
return [{ role: 'user', content: input }];
|
|
74
|
+
};
|
|
75
|
+
export const createAgent = (config) => {
|
|
76
|
+
const tools = config.tools ?? [];
|
|
77
|
+
const build = (input) => {
|
|
78
|
+
const messages = toMessages(input);
|
|
79
|
+
if (!config.system)
|
|
80
|
+
return messages;
|
|
81
|
+
return [{ role: 'system', content: [{ type: 'text', text: config.system }] }, ...messages];
|
|
82
|
+
};
|
|
83
|
+
const stream = (input) => run({ provider: config.provider, model: config.model, tools, messages: build(input), signal: config.signal });
|
|
84
|
+
const runToEnd = async (input) => {
|
|
85
|
+
let message = { role: 'assistant', content: [] };
|
|
86
|
+
let messages = [];
|
|
87
|
+
for await (const event of stream(input)) {
|
|
88
|
+
if (event.type === 'message' && event.message.role === 'assistant')
|
|
89
|
+
message = event.message;
|
|
90
|
+
else if (event.type === 'done')
|
|
91
|
+
messages = event.messages;
|
|
92
|
+
}
|
|
93
|
+
return { message, messages };
|
|
94
|
+
};
|
|
95
|
+
return { stream, run: runToEnd };
|
|
96
|
+
};
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { ContentPart, Message, Provider, Role, StreamEvent, Tool, Usage } from './types.js';
|
|
2
|
+
export { audio, image, text } from './types.js';
|
|
3
|
+
export type { Agent, AgentConfig, AgentResult, Input, LoopEvent, RunOptions } from './agent.js';
|
|
4
|
+
export { createAgent, run } from './agent.js';
|
package/esm/index.js
ADDED
package/esm/package.json
ADDED
package/esm/types.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type ContentPart = {
|
|
2
|
+
type: 'text';
|
|
3
|
+
text: string;
|
|
4
|
+
} | {
|
|
5
|
+
type: 'image';
|
|
6
|
+
mime: string;
|
|
7
|
+
data: Uint8Array;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'audio';
|
|
10
|
+
mime: string;
|
|
11
|
+
data: Uint8Array;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'tool_call';
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
input: unknown;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'tool_result';
|
|
19
|
+
id: string;
|
|
20
|
+
content: ContentPart[];
|
|
21
|
+
};
|
|
22
|
+
export type Role = 'system' | 'user' | 'assistant';
|
|
23
|
+
export type Message = {
|
|
24
|
+
role: Role;
|
|
25
|
+
content: ContentPart[];
|
|
26
|
+
};
|
|
27
|
+
export declare const text: (value: string) => ContentPart;
|
|
28
|
+
export declare const image: (mime: string, data: Uint8Array) => ContentPart;
|
|
29
|
+
export declare const audio: (mime: string, data: Uint8Array) => ContentPart;
|
|
30
|
+
export interface Tool {
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
parameters: Record<string, unknown>;
|
|
34
|
+
prompt?: string;
|
|
35
|
+
run(input: unknown, ctx: {
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}): Promise<ContentPart[]>;
|
|
38
|
+
}
|
|
39
|
+
export interface Usage {
|
|
40
|
+
input?: number;
|
|
41
|
+
output?: number;
|
|
42
|
+
total?: number;
|
|
43
|
+
contextWindow?: number;
|
|
44
|
+
}
|
|
45
|
+
export type StreamEvent = ContentPart | {
|
|
46
|
+
type: 'usage';
|
|
47
|
+
usage: Usage;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'reasoning';
|
|
50
|
+
text: string;
|
|
51
|
+
};
|
|
52
|
+
export interface Provider {
|
|
53
|
+
stream(req: {
|
|
54
|
+
model: string;
|
|
55
|
+
messages: Message[];
|
|
56
|
+
tools: Tool[];
|
|
57
|
+
signal?: AbortSignal;
|
|
58
|
+
}): AsyncIterable<StreamEvent>;
|
|
59
|
+
}
|
package/esm/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mu-core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"main": "./
|
|
7
|
-
"
|
|
3
|
+
"version": "0.16.3",
|
|
4
|
+
"description": "Standalone multimodal agentic loop: content, messages, tools, provider interface, createAgent",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./script/index.js",
|
|
7
|
+
"module": "./esm/index.js",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./esm/index.js",
|
|
11
|
+
"require": "./script/index.js"
|
|
12
|
+
}
|
|
10
13
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
"scripts": {},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"dependencies": {},
|
|
17
|
+
"_generatedBy": "dnt@dev"
|
|
18
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ContentPart, Message, Provider, Tool, Usage } from './types.js';
|
|
2
|
+
export type LoopEvent = ContentPart | {
|
|
3
|
+
type: 'usage';
|
|
4
|
+
usage: Usage;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'reasoning';
|
|
7
|
+
text: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'message';
|
|
10
|
+
message: Message;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'done';
|
|
13
|
+
messages: Message[];
|
|
14
|
+
};
|
|
15
|
+
export interface RunOptions {
|
|
16
|
+
provider: Provider;
|
|
17
|
+
model: string;
|
|
18
|
+
messages: Message[];
|
|
19
|
+
tools?: Tool[];
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
}
|
|
22
|
+
export declare function run(opts: RunOptions): AsyncIterable<LoopEvent>;
|
|
23
|
+
export interface AgentConfig {
|
|
24
|
+
provider: Provider;
|
|
25
|
+
model: string;
|
|
26
|
+
tools?: Tool[];
|
|
27
|
+
system?: string;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
export type Input = string | ContentPart[] | Message[];
|
|
31
|
+
export interface AgentResult {
|
|
32
|
+
message: Message;
|
|
33
|
+
messages: Message[];
|
|
34
|
+
}
|
|
35
|
+
export interface Agent {
|
|
36
|
+
stream(input: Input): AsyncIterable<LoopEvent>;
|
|
37
|
+
run(input: Input): Promise<AgentResult>;
|
|
38
|
+
}
|
|
39
|
+
export declare const createAgent: (config: AgentConfig) => Agent;
|
package/script/agent.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAgent = void 0;
|
|
4
|
+
exports.run = run;
|
|
5
|
+
const append = (parts, part) => {
|
|
6
|
+
const last = parts[parts.length - 1];
|
|
7
|
+
if (part.type === 'text' && last?.type === 'text') {
|
|
8
|
+
last.text += part.text;
|
|
9
|
+
}
|
|
10
|
+
else if (part.type === 'audio' && last?.type === 'audio' && last.mime === part.mime) {
|
|
11
|
+
const merged = new Uint8Array(last.data.length + part.data.length);
|
|
12
|
+
merged.set(last.data);
|
|
13
|
+
merged.set(part.data, last.data.length);
|
|
14
|
+
last.data = merged;
|
|
15
|
+
}
|
|
16
|
+
else if (part.type === 'text') {
|
|
17
|
+
parts.push({ type: 'text', text: part.text });
|
|
18
|
+
}
|
|
19
|
+
else if (part.type === 'audio') {
|
|
20
|
+
parts.push({ type: 'audio', mime: part.mime, data: part.data });
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
parts.push(part);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const execute = async (tools, call, signal) => {
|
|
27
|
+
const tool = tools.get(call.name);
|
|
28
|
+
if (!tool)
|
|
29
|
+
return [{ type: 'text', text: `Unknown tool: ${call.name}` }];
|
|
30
|
+
try {
|
|
31
|
+
return await tool.run(call.input, { signal });
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return [{ type: 'text', text: err instanceof Error ? err.message : String(err) }];
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
async function* run(opts) {
|
|
38
|
+
const { provider, model, signal } = opts;
|
|
39
|
+
const tools = opts.tools ?? [];
|
|
40
|
+
const registry = new Map(tools.map((t) => [t.name, t]));
|
|
41
|
+
const messages = [...opts.messages];
|
|
42
|
+
while (true) {
|
|
43
|
+
const content = [];
|
|
44
|
+
const calls = [];
|
|
45
|
+
for await (const event of provider.stream({ model, messages, tools, signal })) {
|
|
46
|
+
if (event.type === 'usage' || event.type === 'reasoning') {
|
|
47
|
+
yield event;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
yield event;
|
|
51
|
+
append(content, event);
|
|
52
|
+
if (event.type === 'tool_call')
|
|
53
|
+
calls.push(event);
|
|
54
|
+
}
|
|
55
|
+
const message = { role: 'assistant', content };
|
|
56
|
+
messages.push(message);
|
|
57
|
+
yield { type: 'message', message };
|
|
58
|
+
if (calls.length === 0)
|
|
59
|
+
break;
|
|
60
|
+
const results = await Promise.all(calls.map(async (call) => ({
|
|
61
|
+
type: 'tool_result',
|
|
62
|
+
id: call.id,
|
|
63
|
+
content: await execute(registry, call, signal),
|
|
64
|
+
})));
|
|
65
|
+
const toolMessage = { role: 'user', content: results };
|
|
66
|
+
messages.push(toolMessage);
|
|
67
|
+
yield { type: 'message', message: toolMessage };
|
|
68
|
+
}
|
|
69
|
+
yield { type: 'done', messages };
|
|
70
|
+
}
|
|
71
|
+
const isMessages = (input) => input.length > 0 && 'role' in input[0];
|
|
72
|
+
const toMessages = (input) => {
|
|
73
|
+
if (typeof input === 'string')
|
|
74
|
+
return [{ role: 'user', content: [{ type: 'text', text: input }] }];
|
|
75
|
+
if (isMessages(input))
|
|
76
|
+
return input;
|
|
77
|
+
return [{ role: 'user', content: input }];
|
|
78
|
+
};
|
|
79
|
+
const createAgent = (config) => {
|
|
80
|
+
const tools = config.tools ?? [];
|
|
81
|
+
const build = (input) => {
|
|
82
|
+
const messages = toMessages(input);
|
|
83
|
+
if (!config.system)
|
|
84
|
+
return messages;
|
|
85
|
+
return [{ role: 'system', content: [{ type: 'text', text: config.system }] }, ...messages];
|
|
86
|
+
};
|
|
87
|
+
const stream = (input) => run({ provider: config.provider, model: config.model, tools, messages: build(input), signal: config.signal });
|
|
88
|
+
const runToEnd = async (input) => {
|
|
89
|
+
let message = { role: 'assistant', content: [] };
|
|
90
|
+
let messages = [];
|
|
91
|
+
for await (const event of stream(input)) {
|
|
92
|
+
if (event.type === 'message' && event.message.role === 'assistant')
|
|
93
|
+
message = event.message;
|
|
94
|
+
else if (event.type === 'done')
|
|
95
|
+
messages = event.messages;
|
|
96
|
+
}
|
|
97
|
+
return { message, messages };
|
|
98
|
+
};
|
|
99
|
+
return { stream, run: runToEnd };
|
|
100
|
+
};
|
|
101
|
+
exports.createAgent = createAgent;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { ContentPart, Message, Provider, Role, StreamEvent, Tool, Usage } from './types.js';
|
|
2
|
+
export { audio, image, text } from './types.js';
|
|
3
|
+
export type { Agent, AgentConfig, AgentResult, Input, LoopEvent, RunOptions } from './agent.js';
|
|
4
|
+
export { createAgent, run } from './agent.js';
|
package/script/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.run = exports.createAgent = exports.text = exports.image = exports.audio = void 0;
|
|
4
|
+
var types_js_1 = require("./types.js");
|
|
5
|
+
Object.defineProperty(exports, "audio", { enumerable: true, get: function () { return types_js_1.audio; } });
|
|
6
|
+
Object.defineProperty(exports, "image", { enumerable: true, get: function () { return types_js_1.image; } });
|
|
7
|
+
Object.defineProperty(exports, "text", { enumerable: true, get: function () { return types_js_1.text; } });
|
|
8
|
+
var agent_js_1 = require("./agent.js");
|
|
9
|
+
Object.defineProperty(exports, "createAgent", { enumerable: true, get: function () { return agent_js_1.createAgent; } });
|
|
10
|
+
Object.defineProperty(exports, "run", { enumerable: true, get: function () { return agent_js_1.run; } });
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type ContentPart = {
|
|
2
|
+
type: 'text';
|
|
3
|
+
text: string;
|
|
4
|
+
} | {
|
|
5
|
+
type: 'image';
|
|
6
|
+
mime: string;
|
|
7
|
+
data: Uint8Array;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'audio';
|
|
10
|
+
mime: string;
|
|
11
|
+
data: Uint8Array;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'tool_call';
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
input: unknown;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'tool_result';
|
|
19
|
+
id: string;
|
|
20
|
+
content: ContentPart[];
|
|
21
|
+
};
|
|
22
|
+
export type Role = 'system' | 'user' | 'assistant';
|
|
23
|
+
export type Message = {
|
|
24
|
+
role: Role;
|
|
25
|
+
content: ContentPart[];
|
|
26
|
+
};
|
|
27
|
+
export declare const text: (value: string) => ContentPart;
|
|
28
|
+
export declare const image: (mime: string, data: Uint8Array) => ContentPart;
|
|
29
|
+
export declare const audio: (mime: string, data: Uint8Array) => ContentPart;
|
|
30
|
+
export interface Tool {
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
parameters: Record<string, unknown>;
|
|
34
|
+
prompt?: string;
|
|
35
|
+
run(input: unknown, ctx: {
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}): Promise<ContentPart[]>;
|
|
38
|
+
}
|
|
39
|
+
export interface Usage {
|
|
40
|
+
input?: number;
|
|
41
|
+
output?: number;
|
|
42
|
+
total?: number;
|
|
43
|
+
contextWindow?: number;
|
|
44
|
+
}
|
|
45
|
+
export type StreamEvent = ContentPart | {
|
|
46
|
+
type: 'usage';
|
|
47
|
+
usage: Usage;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'reasoning';
|
|
50
|
+
text: string;
|
|
51
|
+
};
|
|
52
|
+
export interface Provider {
|
|
53
|
+
stream(req: {
|
|
54
|
+
model: string;
|
|
55
|
+
messages: Message[];
|
|
56
|
+
tools: Tool[];
|
|
57
|
+
signal?: AbortSignal;
|
|
58
|
+
}): AsyncIterable<StreamEvent>;
|
|
59
|
+
}
|
package/script/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.audio = exports.image = exports.text = void 0;
|
|
4
|
+
const text = (value) => ({ type: 'text', text: value });
|
|
5
|
+
exports.text = text;
|
|
6
|
+
const image = (mime, data) => ({ type: 'image', mime, data });
|
|
7
|
+
exports.image = image;
|
|
8
|
+
const audio = (mime, data) => ({ type: 'audio', mime, data });
|
|
9
|
+
exports.audio = audio;
|
package/README.md
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# mu-core
|
|
2
|
-
|
|
3
|
-
The mu plugin SDK. Provides the agent loop, plugin registry, LLM types, and
|
|
4
|
-
the multi-host primitives (channels, sessions, activity bus, providers).
|
|
5
|
-
Provider implementations are separate packages — for OpenAI-compatible APIs,
|
|
6
|
-
add `mu-openai-provider` and register its plugin alongside.
|
|
7
|
-
|
|
8
|
-
## Install
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
npm install mu-core mu-openai-provider
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## Usage
|
|
15
|
-
|
|
16
|
-
```ts
|
|
17
|
-
import { runAgent, PluginRegistry } from "mu-core";
|
|
18
|
-
import type { ChatMessage, ProviderConfig } from "mu-core";
|
|
19
|
-
import { createOpenAIProviderPlugin } from "mu-openai-provider";
|
|
20
|
-
|
|
21
|
-
const config: ProviderConfig = {
|
|
22
|
-
baseUrl: "http://localhost:11434/v1",
|
|
23
|
-
maxTokens: 4096,
|
|
24
|
-
temperature: 0.7,
|
|
25
|
-
streamTimeoutMs: 30000,
|
|
26
|
-
// providerId defaults to 'openai' — register at least one provider
|
|
27
|
-
// implementation (e.g. via createOpenAIProviderPlugin) before running.
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const registry = new PluginRegistry({ cwd: process.cwd(), config: {} });
|
|
31
|
-
await registry.register(createOpenAIProviderPlugin());
|
|
32
|
-
|
|
33
|
-
const messages: ChatMessage[] = [
|
|
34
|
-
{ role: "user", content: "Hello" },
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const controller = new AbortController();
|
|
38
|
-
|
|
39
|
-
for await (const event of runAgent(messages, config, "qwen2.5", controller.signal, registry)) {
|
|
40
|
-
if (event.type === "content") process.stdout.write(event.text);
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
For a higher-level API that owns conversation state, channel I/O, and
|
|
45
|
-
multi-session lifecycle, see `startMu` and `Session` (`createSessionManager`).
|
|
46
|
-
|
|
47
|
-
## Plugin System
|
|
48
|
-
|
|
49
|
-
Plugins can provide tools, system prompts, lifecycle hooks, slash commands,
|
|
50
|
-
custom agent loops, and side-channel registries (channels, providers,
|
|
51
|
-
activity bus, agent sources).
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
import type { Plugin } from "mu-core";
|
|
55
|
-
|
|
56
|
-
const myPlugin: Plugin = {
|
|
57
|
-
name: "my-plugin",
|
|
58
|
-
tools: [
|
|
59
|
-
{
|
|
60
|
-
definition: {
|
|
61
|
-
type: "function",
|
|
62
|
-
function: {
|
|
63
|
-
name: "hello",
|
|
64
|
-
description: "Say hello",
|
|
65
|
-
parameters: { type: "object", properties: {} },
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
execute: async () => "Hello, world!",
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
hooks: {
|
|
72
|
-
beforeLlmCall: (messages, config) => messages,
|
|
73
|
-
afterLlmCall: (result) => result,
|
|
74
|
-
beforeToolExec: (toolCall) => toolCall,
|
|
75
|
-
afterToolExec: (toolCall, result) => result,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
await registry.register(myPlugin);
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Filesystem and shell tools live in `mu-coding` (`createCodingToolsPlugin`),
|
|
83
|
-
not in mu-core — keeps the SDK host-agnostic.
|
|
84
|
-
|
|
85
|
-
## API
|
|
86
|
-
|
|
87
|
-
### Agent loop
|
|
88
|
-
- `runAgent(messages, config, model, signal, registry)` — async generator yielding `AgentEvent` (`content`, `reasoning`, `messages`, `usage`, `turn_end`).
|
|
89
|
-
- Provider resolution: looks up `config.providerId ?? 'openai'` in the registered `ProviderRegistry`. Throws if no provider is registered.
|
|
90
|
-
|
|
91
|
-
### Sessions
|
|
92
|
-
- `createSessionManager({ registry, config, model })` returns a `SessionManager`.
|
|
93
|
-
- `session.runTurn({ userMessage, ... })` — appends, drains queue, runs agent loop, emits events.
|
|
94
|
-
- `session.subscribe(listener)` — `messages_changed`, `stream_partial`, `stream_started`, `stream_ended`, `usage`, `error`.
|
|
95
|
-
|
|
96
|
-
### Channels
|
|
97
|
-
- `Channel` interface (`id`, `start`, `stop`) — input surfaces (TUI, Telegram, websocket).
|
|
98
|
-
- `createChannelRegistry()` — host-managed registry; `startAll()` / `stopAll()` for lifecycle.
|
|
99
|
-
|
|
100
|
-
### Providers
|
|
101
|
-
- `ProviderAdapter` + `createProvider(adapter)` — build a `Provider` from raw HTTP semantics.
|
|
102
|
-
- `readSSE`, `readNDJSON`, `fetchWithIdleTimeout` — transport primitives.
|
|
103
|
-
- `ProviderRegistry` — host-managed; populated by provider plugins.
|
|
104
|
-
|
|
105
|
-
### Host
|
|
106
|
-
- `startMu(options)` — generic bootstrap: loads config, builds registries, activates plugins (config-listed via `options.resolvePlugin`, then code-passed), starts channels.
|
|
107
|
-
|
|
108
|
-
## License
|
|
109
|
-
|
|
110
|
-
MIT
|
package/src/activity.test.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'bun:test';
|
|
2
|
-
import { createActivityBus } from './activity';
|
|
3
|
-
|
|
4
|
-
describe('ActivityBus', () => {
|
|
5
|
-
it('emits events to multiple subscribers', () => {
|
|
6
|
-
const bus = createActivityBus();
|
|
7
|
-
const a: string[] = [];
|
|
8
|
-
const b: string[] = [];
|
|
9
|
-
bus.subscribe((e) => a.push(e.summary));
|
|
10
|
-
bus.subscribe((e) => b.push(e.summary));
|
|
11
|
-
bus.emit('tool_start', 'bash', 'running git status');
|
|
12
|
-
expect(a).toEqual(['running git status']);
|
|
13
|
-
expect(b).toEqual(['running git status']);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('unsubscribes', () => {
|
|
17
|
-
const bus = createActivityBus();
|
|
18
|
-
const seen: string[] = [];
|
|
19
|
-
const off = bus.subscribe((e) => seen.push(e.summary));
|
|
20
|
-
bus.emit('tool_start', 'bash', 'first');
|
|
21
|
-
off();
|
|
22
|
-
bus.emit('tool_start', 'bash', 'second');
|
|
23
|
-
expect(seen).toEqual(['first']);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('subagent stream is independent', () => {
|
|
27
|
-
const bus = createActivityBus();
|
|
28
|
-
const sub: string[] = [];
|
|
29
|
-
bus.subscribeSubAgent((e) => sub.push(e.kind));
|
|
30
|
-
bus.emitSubAgent({ runId: 'r1', agentId: 'review', kind: 'invocation_start', ts: 1, data: {} });
|
|
31
|
-
expect(sub).toEqual(['invocation_start']);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('throwing listener does not break the bus', () => {
|
|
35
|
-
const bus = createActivityBus();
|
|
36
|
-
bus.subscribe(() => {
|
|
37
|
-
throw new Error('boom');
|
|
38
|
-
});
|
|
39
|
-
const seen: string[] = [];
|
|
40
|
-
bus.subscribe((e) => seen.push(e.summary));
|
|
41
|
-
bus.emit('tool_end', 'bash', 'ok');
|
|
42
|
-
expect(seen).toEqual(['ok']);
|
|
43
|
-
});
|
|
44
|
-
});
|