pulse-coder-engine 0.0.1-alpha.1 → 0.0.1-alpha.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.
@@ -1,134 +0,0 @@
1
- import { pruneMessages, type ModelMessage } from "ai";
2
- import { summarizeMessages } from "../ai";
3
- import {
4
- COMPACT_TRIGGER,
5
- COMPACT_TARGET,
6
- KEEP_LAST_TURNS,
7
- } from "../config/index";
8
- import type { Context } from "../shared/types";
9
-
10
- type CompactResult = {
11
- didCompact: boolean;
12
- reason?: string;
13
- newMessages?: ModelMessage[];
14
- };
15
-
16
- const ensureSummaryPrefix = (summary: string): string => {
17
- const trimmed = summary.trim();
18
- if (trimmed.length === 0) {
19
- return '';
20
- }
21
- if (trimmed.startsWith('[COMPACTED_CONTEXT]')) {
22
- return trimmed;
23
- }
24
- return `[COMPACTED_CONTEXT]\n${trimmed}`;
25
- };
26
-
27
- const safeStringify = (value: unknown): string => {
28
- try {
29
- return JSON.stringify(value);
30
- } catch {
31
- return String(value);
32
- }
33
- };
34
-
35
- const estimateTokens = (messages: ModelMessage[]): number => {
36
- let totalChars = 0;
37
- for (const message of messages) {
38
- totalChars += message.role.length;
39
- if (typeof message.content === 'string') {
40
- totalChars += message.content.length;
41
- } else {
42
- totalChars += safeStringify(message.content).length;
43
- }
44
- }
45
- return Math.ceil(totalChars / 4);
46
- };
47
-
48
- const splitByTurns = (messages: ModelMessage[], keepLastTurns: number) => {
49
- const userIndices: number[] = [];
50
- messages.forEach((message, index) => {
51
- if (message.role === 'user') {
52
- userIndices.push(index);
53
- }
54
- });
55
-
56
- if (userIndices.length <= keepLastTurns) {
57
- return { oldMessages: [], recentMessages: messages };
58
- }
59
-
60
- const cutIndex = userIndices[userIndices.length - keepLastTurns];
61
- return {
62
- oldMessages: messages.slice(0, cutIndex),
63
- recentMessages: messages.slice(cutIndex),
64
- };
65
- };
66
-
67
- const takeLastTurns = (messages: ModelMessage[], keepLastTurns: number): ModelMessage[] => {
68
- const userIndices: number[] = [];
69
- messages.forEach((message, index) => {
70
- if (message.role === 'user') {
71
- userIndices.push(index);
72
- }
73
- });
74
-
75
- if (userIndices.length === 0) {
76
- return messages;
77
- }
78
-
79
- if (userIndices.length <= keepLastTurns) {
80
- return messages;
81
- }
82
-
83
- const startIndex = userIndices[userIndices.length - keepLastTurns];
84
- return messages.slice(startIndex);
85
- };
86
-
87
- export const maybeCompactContext = async (
88
- context: Context,
89
- options?: { force?: boolean }
90
- ): Promise<CompactResult> => {
91
- const { messages } = context;
92
- if (messages.length === 0) {
93
- return { didCompact: false };
94
- }
95
-
96
- const estimatedTokens = estimateTokens(messages);
97
- if (!options?.force && estimatedTokens < COMPACT_TRIGGER) {
98
- return { didCompact: false };
99
- }
100
-
101
- const { oldMessages, recentMessages } = splitByTurns(messages, KEEP_LAST_TURNS);
102
- if (oldMessages.length === 0) {
103
- return { didCompact: false };
104
- }
105
-
106
- try {
107
- const summary = await summarizeMessages(oldMessages);
108
- const summaryText = ensureSummaryPrefix(summary);
109
- if (!summaryText) {
110
- throw new Error('Empty summary result');
111
- }
112
-
113
- const nextMessages: ModelMessage[] = [
114
- { role: 'assistant', content: summaryText },
115
- ...recentMessages,
116
- ];
117
-
118
- if (estimateTokens(nextMessages) > COMPACT_TARGET) {
119
- const newMessages = takeLastTurns(messages, KEEP_LAST_TURNS);
120
- return { didCompact: true, reason: 'summary-too-large', newMessages };
121
- }
122
-
123
- return { didCompact: true, newMessages: nextMessages };
124
- } catch (error) {
125
- const pruned = pruneMessages({
126
- messages,
127
- reasoning: 'all',
128
- toolCalls: 'all',
129
- emptyMessages: 'remove',
130
- });
131
- const newMessages = takeLastTurns(pruned, KEEP_LAST_TURNS);
132
- return { didCompact: true, reason: 'fallback', newMessages };
133
- }
134
- };
package/src/core/loop.ts DELETED
@@ -1,147 +0,0 @@
1
- import { ToolSet, type StepResult, type ModelMessage } from "ai";
2
- import type { Context, ClarificationRequest, Tool } from "../shared/types";
3
- import { streamTextAI } from "../ai";
4
- import { maybeCompactContext } from "../context";
5
- import {
6
- MAX_COMPACTION_ATTEMPTS,
7
- MAX_ERROR_COUNT,
8
- MAX_STEPS
9
- } from "../config/index.js";
10
-
11
- export interface LoopOptions {
12
- onText?: (delta: string) => void;
13
- onToolCall?: (toolCall: any) => void;
14
- onToolResult?: (toolResult: any) => void;
15
- onStepFinish?: (step: StepResult<any>) => void;
16
- onClarificationRequest?: (request: ClarificationRequest) => Promise<string>;
17
- onCompacted?: (newMessages: ModelMessage[]) => void;
18
- onResponse?: (messages: StepResult<ToolSet>['response']['messages']) => void;
19
- abortSignal?: AbortSignal;
20
-
21
- tools?: Record<string, Tool>; // 允许传入工具覆盖默认工具
22
- }
23
-
24
- export async function loop(context: Context, options?: LoopOptions): Promise<string> {
25
- let errorCount = 0;
26
- let totalSteps = 0;
27
- let compactionAttempts = 0;
28
-
29
- while (true) {
30
- try {
31
- if (compactionAttempts < MAX_COMPACTION_ATTEMPTS) {
32
- const { didCompact, newMessages } = await maybeCompactContext(context);
33
- if (didCompact) {
34
- compactionAttempts++;
35
-
36
- if (newMessages) {
37
- options?.onCompacted?.(newMessages)
38
- }
39
- continue;
40
- }
41
- }
42
-
43
- const tools = options?.tools || {}; // 允许传入工具覆盖默认工具
44
-
45
- // Prepare tool execution context
46
- const toolExecutionContext = {
47
- onClarificationRequest: options?.onClarificationRequest,
48
- abortSignal: options?.abortSignal
49
- };
50
-
51
- const result = streamTextAI(context.messages, tools, {
52
- abortSignal: options?.abortSignal,
53
- toolExecutionContext,
54
- onStepFinish: (step) => {
55
- options?.onStepFinish?.(step);
56
- },
57
- onChunk: ({ chunk }) => {
58
- if (chunk.type === 'text-delta') {
59
- options?.onText?.(chunk.text);
60
- }
61
- if (chunk.type === 'tool-call') {
62
- options?.onToolCall?.(chunk);
63
- }
64
- if (chunk.type === 'tool-result') {
65
- options?.onToolResult?.(chunk);
66
- }
67
- },
68
- });
69
-
70
- const [text, steps, finishReason] = await Promise.all([
71
- result.text,
72
- result.steps,
73
- result.finishReason,
74
- ]);
75
-
76
- totalSteps += steps.length;
77
-
78
- for (const step of steps) {
79
- if (step.response?.messages?.length) {
80
- const messages = [...step.response.messages];
81
- options?.onResponse?.(messages);
82
- }
83
- }
84
-
85
- if (finishReason === 'stop') {
86
- return text || 'Task completed.';
87
- }
88
-
89
- if (finishReason === 'length') {
90
- if (compactionAttempts < MAX_COMPACTION_ATTEMPTS) {
91
- const { didCompact, newMessages } = await maybeCompactContext(context, { force: true });
92
- if (didCompact) {
93
- compactionAttempts++;
94
- if (newMessages) {
95
- options?.onCompacted?.(newMessages)
96
- }
97
- continue;
98
- }
99
- }
100
- return text || 'Context limit reached.';
101
- }
102
-
103
- if (finishReason === 'content-filter') {
104
- return text || 'Content filtered.';
105
- }
106
-
107
- if (finishReason === 'error') {
108
- return text || 'Task failed.';
109
- }
110
-
111
- if (finishReason === 'tool-calls') {
112
- if (totalSteps >= MAX_STEPS) {
113
- return text || 'Max steps reached, task may be incomplete.';
114
- }
115
- continue;
116
- }
117
-
118
- return text || 'Task completed.';
119
- } catch (error: any) {
120
- if (options?.abortSignal?.aborted || error?.name === 'AbortError') {
121
- return 'Request aborted.';
122
- }
123
-
124
- errorCount++;
125
- if (errorCount >= MAX_ERROR_COUNT) {
126
- return `Failed after ${errorCount} errors: ${error?.message ?? String(error)}`;
127
- }
128
-
129
- if (isRetryableError(error)) {
130
- const delay = Math.min(2000 * Math.pow(2, errorCount - 1), 30000);
131
- await sleep(delay);
132
- continue;
133
- }
134
-
135
- return `Error: ${error?.message ?? String(error)}`;
136
- }
137
- }
138
- }
139
-
140
- function isRetryableError(error: any): boolean {
141
- const status = error?.status ?? error?.statusCode;
142
- return status === 429 || status === 500 || status === 502 || status === 503;
143
- }
144
-
145
- function sleep(ms: number): Promise<void> {
146
- return new Promise((resolve) => setTimeout(resolve, ms));
147
- }
package/src/index.ts DELETED
@@ -1,17 +0,0 @@
1
- export { Engine } from './Engine.js';
2
- export { Engine as PulseAgent } from './Engine.js'; // 添加 PulseAgent 别名
3
-
4
- // 插件系统导出
5
- export type { EnginePlugin, EnginePluginContext } from './plugin/EnginePlugin.js';
6
- export type { UserConfigPlugin, UserConfigPluginLoadOptions } from './plugin/UserConfigPlugin.js';
7
- export { PluginManager } from './plugin/PluginManager.js';
8
-
9
- // 内置插件导出
10
- export { builtInPlugins, builtInMCPPlugin, builtInSkillsPlugin, BuiltInSkillRegistry } from './built-in/index.js';
11
-
12
- // 原有导出保持不变
13
- export * from './shared/types.js';
14
- export { loop } from './core/loop.js';
15
- export { streamTextAI } from './ai/index.js';
16
- export { maybeCompactContext } from './context/index.js';
17
- export * from './tools/index.js';
@@ -1,60 +0,0 @@
1
- import type { Tool } from 'ai';
2
- import type { EventEmitter } from 'events';
3
-
4
- // 修复:确保类型正确导出
5
- export interface EnginePlugin {
6
- name: string;
7
- version: string;
8
- dependencies?: string[];
9
-
10
- // 生命周期钩子 - 添加类型标注
11
- beforeInitialize?(context: EnginePluginContext): Promise<void>;
12
- initialize(context: EnginePluginContext): Promise<void>;
13
- afterInitialize?(context: EnginePluginContext): Promise<void>;
14
-
15
- // 清理钩子
16
- destroy?(context: EnginePluginContext): Promise<void>;
17
- }
18
-
19
- export interface EnginePluginContext {
20
- registerTool(name: string, tool: Tool): void;
21
- registerTools(tools: Record<string, Tool>): void;
22
- getTool(name: string): Tool | undefined;
23
-
24
- registerProtocol(name: string, handler: ProtocolHandler): void;
25
- getProtocol(name: string): ProtocolHandler | undefined;
26
-
27
- registerService<T>(name: string, service: T): void;
28
- getService<T>(name: string): T | undefined;
29
-
30
- getConfig<T>(key: string): T | undefined;
31
- setConfig<T>(key: string, value: T): void;
32
-
33
- events: EventEmitter;
34
-
35
- logger: {
36
- debug(message: string, meta?: any): void;
37
- info(message: string, meta?: any): void;
38
- warn(message: string, meta?: any): void;
39
- error(message: string, error?: Error, meta?: any): void;
40
- };
41
- }
42
-
43
- export interface ProtocolHandler {
44
- name: string;
45
- handle(message: any): Promise<any>;
46
- }
47
-
48
- export interface EnginePluginLoadOptions {
49
- plugins?: EnginePlugin[];
50
- dirs?: string[];
51
- scan?: boolean;
52
- }
53
-
54
- export const DEFAULT_ENGINE_PLUGIN_DIRS = [
55
- '.pulse-coder/engine-plugins',
56
- '.coder/engine-plugins',
57
- '~/.pulse-coder/engine-plugins',
58
- '~/.coder/engine-plugins',
59
- './plugins/engine'
60
- ];