@cicctencent/agent-core 0.1.1

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.
Files changed (134) hide show
  1. package/README.md +952 -0
  2. package/dist/agent/engine.d.ts +113 -0
  3. package/dist/agent/engine.d.ts.map +1 -0
  4. package/dist/agent/history-manager.d.ts +78 -0
  5. package/dist/agent/history-manager.d.ts.map +1 -0
  6. package/dist/agent/llm-caller.d.ts +58 -0
  7. package/dist/agent/llm-caller.d.ts.map +1 -0
  8. package/dist/agent/tool-executor.d.ts +95 -0
  9. package/dist/agent/tool-executor.d.ts.map +1 -0
  10. package/dist/agent/types.d.ts +128 -0
  11. package/dist/agent/types.d.ts.map +1 -0
  12. package/dist/agent/workflow.d.ts +74 -0
  13. package/dist/agent/workflow.d.ts.map +1 -0
  14. package/dist/context/collector.d.ts +12 -0
  15. package/dist/context/collector.d.ts.map +1 -0
  16. package/dist/context/manager.d.ts +16 -0
  17. package/dist/context/manager.d.ts.map +1 -0
  18. package/dist/context/types.d.ts +39 -0
  19. package/dist/context/types.d.ts.map +1 -0
  20. package/dist/hooks/types.d.ts +47 -0
  21. package/dist/hooks/types.d.ts.map +1 -0
  22. package/dist/index.d.ts +53 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/llm/anthropic-provider.d.ts +28 -0
  25. package/dist/llm/anthropic-provider.d.ts.map +1 -0
  26. package/dist/llm/factory.d.ts +19 -0
  27. package/dist/llm/factory.d.ts.map +1 -0
  28. package/dist/llm/ollama-provider.d.ts +13 -0
  29. package/dist/llm/ollama-provider.d.ts.map +1 -0
  30. package/dist/llm/openai-provider.d.ts +18 -0
  31. package/dist/llm/openai-provider.d.ts.map +1 -0
  32. package/dist/llm/provider.d.ts +7 -0
  33. package/dist/llm/provider.d.ts.map +1 -0
  34. package/dist/llm/types.d.ts +78 -0
  35. package/dist/llm/types.d.ts.map +1 -0
  36. package/dist/mcp/base-client.d.ts +50 -0
  37. package/dist/mcp/base-client.d.ts.map +1 -0
  38. package/dist/mcp/client.d.ts +27 -0
  39. package/dist/mcp/client.d.ts.map +1 -0
  40. package/dist/mcp/factory.d.ts +16 -0
  41. package/dist/mcp/factory.d.ts.map +1 -0
  42. package/dist/mcp/http-client.d.ts +24 -0
  43. package/dist/mcp/http-client.d.ts.map +1 -0
  44. package/dist/mcp/proxy-agent.d.ts +49 -0
  45. package/dist/mcp/proxy-agent.d.ts.map +1 -0
  46. package/dist/mcp/stdio-client.d.ts +16 -0
  47. package/dist/mcp/stdio-client.d.ts.map +1 -0
  48. package/dist/mcp/types.d.ts +60 -0
  49. package/dist/mcp/types.d.ts.map +1 -0
  50. package/dist/memory/checkpoint.d.ts +62 -0
  51. package/dist/memory/checkpoint.d.ts.map +1 -0
  52. package/dist/memory/extractor.d.ts +59 -0
  53. package/dist/memory/extractor.d.ts.map +1 -0
  54. package/dist/memory/persona.d.ts +38 -0
  55. package/dist/memory/persona.d.ts.map +1 -0
  56. package/dist/memory/pipeline.d.ts +50 -0
  57. package/dist/memory/pipeline.d.ts.map +1 -0
  58. package/dist/memory/prompts.d.ts +9 -0
  59. package/dist/memory/prompts.d.ts.map +1 -0
  60. package/dist/memory/recall.d.ts +66 -0
  61. package/dist/memory/recall.d.ts.map +1 -0
  62. package/dist/memory/scene.d.ts +49 -0
  63. package/dist/memory/scene.d.ts.map +1 -0
  64. package/dist/memory/types.d.ts +43 -0
  65. package/dist/memory/types.d.ts.map +1 -0
  66. package/dist/middleware/types.d.ts +52 -0
  67. package/dist/middleware/types.d.ts.map +1 -0
  68. package/dist/sandbox/types.d.ts +59 -0
  69. package/dist/sandbox/types.d.ts.map +1 -0
  70. package/dist/security/guard.d.ts +41 -0
  71. package/dist/security/guard.d.ts.map +1 -0
  72. package/dist/security/types.d.ts +36 -0
  73. package/dist/security/types.d.ts.map +1 -0
  74. package/dist/skill/manager.d.ts +95 -0
  75. package/dist/skill/manager.d.ts.map +1 -0
  76. package/dist/skill/router.d.ts +26 -0
  77. package/dist/skill/router.d.ts.map +1 -0
  78. package/dist/skill/types.d.ts +55 -0
  79. package/dist/skill/types.d.ts.map +1 -0
  80. package/dist/tool/delegate.d.ts +34 -0
  81. package/dist/tool/delegate.d.ts.map +1 -0
  82. package/dist/tool/registry.d.ts +24 -0
  83. package/dist/tool/registry.d.ts.map +1 -0
  84. package/dist/tool/types.d.ts +101 -0
  85. package/dist/tool/types.d.ts.map +1 -0
  86. package/dist/types.d.ts +150 -0
  87. package/dist/types.d.ts.map +1 -0
  88. package/dist/utils/token-counter.d.ts +22 -0
  89. package/dist/utils/token-counter.d.ts.map +1 -0
  90. package/package.json +42 -0
  91. package/src/agent/engine.ts +875 -0
  92. package/src/agent/history-manager.ts +323 -0
  93. package/src/agent/llm-caller.ts +383 -0
  94. package/src/agent/tool-executor.ts +713 -0
  95. package/src/agent/types.ts +136 -0
  96. package/src/agent/workflow.ts +339 -0
  97. package/src/context/collector.ts +59 -0
  98. package/src/context/manager.ts +40 -0
  99. package/src/context/types.ts +51 -0
  100. package/src/hooks/types.ts +77 -0
  101. package/src/index.ts +178 -0
  102. package/src/llm/anthropic-provider.ts +412 -0
  103. package/src/llm/factory.ts +34 -0
  104. package/src/llm/ollama-provider.ts +39 -0
  105. package/src/llm/openai-provider.ts +200 -0
  106. package/src/llm/provider.ts +15 -0
  107. package/src/llm/types.ts +91 -0
  108. package/src/mcp/base-client.ts +159 -0
  109. package/src/mcp/client.ts +81 -0
  110. package/src/mcp/factory.ts +45 -0
  111. package/src/mcp/http-client.ts +149 -0
  112. package/src/mcp/proxy-agent.ts +227 -0
  113. package/src/mcp/stdio-client.ts +104 -0
  114. package/src/mcp/types.ts +65 -0
  115. package/src/memory/checkpoint.ts +146 -0
  116. package/src/memory/extractor.ts +164 -0
  117. package/src/memory/persona.ts +106 -0
  118. package/src/memory/pipeline.ts +192 -0
  119. package/src/memory/prompts.ts +73 -0
  120. package/src/memory/recall.ts +217 -0
  121. package/src/memory/scene.ts +137 -0
  122. package/src/memory/types.ts +54 -0
  123. package/src/middleware/types.ts +62 -0
  124. package/src/sandbox/types.ts +185 -0
  125. package/src/security/guard.ts +249 -0
  126. package/src/security/types.ts +46 -0
  127. package/src/skill/manager.ts +420 -0
  128. package/src/skill/router.ts +158 -0
  129. package/src/skill/types.ts +67 -0
  130. package/src/tool/delegate.ts +105 -0
  131. package/src/tool/registry.ts +64 -0
  132. package/src/tool/types.ts +124 -0
  133. package/src/types.ts +188 -0
  134. package/src/utils/token-counter.ts +108 -0
@@ -0,0 +1,77 @@
1
+ // ============================================================
2
+ // Hooks — 生命周期钩子类型定义
3
+ // ============================================================
4
+
5
+ import type { Message, TokenUsage } from '../types.ts';
6
+ import type { ToolDefinition } from '../tool/types.ts';
7
+
8
+ // ---- Hook 事件类型(7 个)----
9
+
10
+ export type HookEvent =
11
+ | 'before_tool' // 工具执行前(可阻止/修改参数)
12
+ | 'after_tool' // 工具执行后(可注入信息到结果)
13
+ | 'before_ll_call' // LLM 调用前(可审查 messages/tools)
14
+ | 'after_ll_call' // LLM 调用后(可审查响应)
15
+ | 'on_iteration_start' // 每轮迭代开始(可注入上下文信息)
16
+ | 'on_error' // 任何错误发生时
17
+ | 'on_complete'; // Agent Loop 完成时
18
+
19
+ // ---- Hook 上下文 ----
20
+
21
+ /**
22
+ * Hook 处理器接收的上下文信息
23
+ * 不同事件携带不同的字段组合
24
+ */
25
+ export interface HookContext {
26
+ sessionId: string;
27
+ iteration: number;
28
+ traceId?: string;
29
+
30
+ // before_tool / after_tool
31
+ tool?: string;
32
+ args?: Record<string, unknown>;
33
+ result?: string;
34
+ isError?: boolean;
35
+ duration?: number;
36
+
37
+ // before_ll_call / after_ll_call
38
+ messages?: Message[];
39
+ tools?: ToolDefinition[];
40
+ response?: Message;
41
+
42
+ // on_error
43
+ error?: string;
44
+ recoverable?: boolean;
45
+
46
+ // on_complete
47
+ totalIterations?: number;
48
+ usage?: TokenUsage;
49
+ }
50
+
51
+ // ---- Hook 返回值 ----
52
+
53
+ /**
54
+ * Hook 处理器的返回值
55
+ * 仅 before_tool 和 after_tool 的返回值有实际效果
56
+ */
57
+ export interface HookResult {
58
+ /** before_tool: 阻止工具执行 */
59
+ block?: boolean;
60
+ /** block 时的原因(返回给 LLM) */
61
+ reason?: string;
62
+ /** before_tool: 修改工具参数(与原始 args 合并) */
63
+ modify?: Record<string, unknown>;
64
+ /** after_tool: 追加到工具结果末尾 */
65
+ inject?: string;
66
+ }
67
+
68
+ // ---- Hook 定义 ----
69
+
70
+ export interface Hook {
71
+ /** Hook 名称(用于日志和调试) */
72
+ name: string;
73
+ /** 监听的事件类型 */
74
+ event: HookEvent;
75
+ /** 处理器函数 */
76
+ handler: (ctx: HookContext) => Promise<HookResult | void>;
77
+ }
package/src/index.ts ADDED
@@ -0,0 +1,178 @@
1
+ // ============================================================
2
+ // Core 统一导出
3
+ // ============================================================
4
+
5
+ // ---- 全局类型 ----
6
+ export type {
7
+ Message,
8
+ MessageRole,
9
+ ToolCall,
10
+ AgentEvent,
11
+ BaseEvent,
12
+ ThinkingEvent,
13
+ ToolCallEvent,
14
+ ToolResultEvent,
15
+ MessageEvent,
16
+ DoneEvent,
17
+ ErrorEvent,
18
+ SteeringEvent,
19
+ RetryEvent,
20
+ AgentResult,
21
+ AgentStatus,
22
+ SessionContext,
23
+ TokenUsage,
24
+ ToolCallRecord,
25
+ } from './types.ts';
26
+
27
+ // ---- Agent Engine ----
28
+ export { AgentEngine } from './agent/engine.ts';
29
+ export {
30
+ DEFAULT_COMPLEX_WORKFLOW_TEMPLATE,
31
+ buildDebateInstructions,
32
+ buildStructuredOutputInstruction,
33
+ buildWorkflowDecisionMemory,
34
+ formatDebateOutput,
35
+ formatStructuredAgentOutput,
36
+ formatWorkflowTemplateForPrompt,
37
+ getWorkflowRoleStage,
38
+ getWorkflowTemplate,
39
+ parseDebateOutput,
40
+ parseStructuredAgentOutput,
41
+ wrapTaskWithWorkflowInstructions,
42
+ } from './agent/workflow.ts';
43
+ export type { StructuredAgentOutput, WorkflowCheckpoint, WorkflowDecisionLog, WorkflowRole, WorkflowStageTemplate, WorkflowTemplate, DebateOutput } from './agent/workflow.ts';
44
+ export { HistoryManager } from './agent/history-manager.ts';
45
+ export { LLMCaller } from './agent/llm-caller.ts';
46
+ export { AgentToolExecutor } from './agent/tool-executor.ts';
47
+ export type { AgentConfig, RunOptions, RetryPolicy, ToolResultStore, OffloadEntry } from './agent/types.ts';
48
+
49
+ // ---- LLM ----
50
+ export type {
51
+ LLMProvider,
52
+ ModelConfig,
53
+ ModelInfo,
54
+ ChatParams,
55
+ ChatResponse,
56
+ ChatChunk,
57
+ } from './llm/types.ts';
58
+ export { OpenAIProvider } from './llm/openai-provider.ts';
59
+ export { AnthropicProvider } from './llm/anthropic-provider.ts';
60
+ export { OllamaProvider } from './llm/ollama-provider.ts';
61
+ export { createLLMProvider, BUILTIN_PROVIDERS } from './llm/factory.ts';
62
+ export type { BuiltinProvider } from './llm/factory.ts';
63
+
64
+ // ---- Context ----
65
+ export { ContextManager } from './context/manager.ts';
66
+ export { LocalEnvCollector } from './context/collector.ts';
67
+ export type {
68
+ BuildContextOptions,
69
+ EnvInfo,
70
+ EnvCollector,
71
+ } from './context/types.ts';
72
+
73
+ // ---- Skill ----
74
+ export { SkillManager } from './skill/manager.ts';
75
+ export { SkillRouter } from './skill/router.ts';
76
+ export type {
77
+ Skill,
78
+ SkillManifest,
79
+ SkillSource,
80
+ RouteResult,
81
+ RouteStrategy,
82
+ MatchType,
83
+ } from './skill/types.ts';
84
+
85
+ // ---- Tool ----
86
+ export { ToolRegistry } from './tool/registry.ts';
87
+ export { createDelegateTool } from './tool/delegate.ts';
88
+ export type { SubAgentRunner } from './tool/delegate.ts';
89
+ export type {
90
+ ToolDefinition,
91
+ ToolExecutor,
92
+ ToolContext,
93
+ SubAgentEventEmitter,
94
+ WidgetEventEmitter,
95
+ ArtifactEventEmitter,
96
+ ArtifactData,
97
+ RegisteredTool,
98
+ ToolCategory,
99
+ ShellExecutor,
100
+ FileSystemExecutor,
101
+ } from './tool/types.ts';
102
+
103
+ // ---- MCP ----
104
+ export { registerMCPTools, MCP_TOOL_PREFIX, buildMCPToolName, sanitizeForToolName } from './mcp/client.ts';
105
+ export { createMCPClient, registerTransport } from './mcp/factory.ts';
106
+ export type { TransportFactory } from './mcp/factory.ts';
107
+ export { MCPClientBase } from './mcp/base-client.ts';
108
+ export { StdioMCPClient } from './mcp/stdio-client.ts';
109
+ export { HttpMCPClient } from './mcp/http-client.ts';
110
+ export {
111
+ resolveProxyAgentForCategory,
112
+ resolveProxyUrlForCategory,
113
+ getSystemProxyUrl,
114
+ clearSystemProxyCache,
115
+ getWindowsSystemProxy,
116
+ DEFAULT_PROXY_SETTINGS,
117
+ } from './mcp/proxy-agent.ts';
118
+ export type {
119
+ ProxyMode,
120
+ ProxyCategoryConfig,
121
+ ProxySettings,
122
+ } from './mcp/proxy-agent.ts';
123
+ export type {
124
+ MCPClient,
125
+ MCPServerConfig,
126
+ MCPTransport,
127
+ } from './mcp/types.ts';
128
+
129
+ // ---- Security ----
130
+ export { SecurityGuard } from './security/guard.ts';
131
+ export type {
132
+ SanitizeResult,
133
+ SecurityRule,
134
+ SecurityPolicy,
135
+ Severity,
136
+ } from './security/types.ts';
137
+
138
+ // ---- Sandbox ----
139
+ export type {
140
+ Sandbox,
141
+ SandboxConfig,
142
+ SandboxManager,
143
+ ScriptLanguage,
144
+ } from './sandbox/types.ts';
145
+ export { SANDBOX_BLOCKED_COMMANDS, SANDBOX_SAFE_ENV_KEYS, DEFAULT_SANDBOX_CONFIG } from './sandbox/types.ts';
146
+
147
+ // ---- Hooks ----
148
+ export type {
149
+ Hook,
150
+ HookEvent,
151
+ HookContext,
152
+ HookResult,
153
+ } from './hooks/types.ts';
154
+
155
+ // ---- Middleware ----
156
+ export type {
157
+ Middleware,
158
+ MiddlewareContext,
159
+ } from './middleware/types.ts';
160
+
161
+ // ---- Memory ----
162
+ export type {
163
+ MemoryEntry,
164
+ MemoryLayer,
165
+ MemoryServiceLike,
166
+ } from './memory/types.ts';
167
+ export { MemoryExtractor } from './memory/extractor.ts';
168
+ export type { AtomFact, ExtractorOptions } from './memory/extractor.ts';
169
+ export { SceneAggregator } from './memory/scene.ts';
170
+ export type { SceneBlock, SceneAggregatorOptions } from './memory/scene.ts';
171
+ export { PersonaGenerator } from './memory/persona.ts';
172
+ export type { PersonaFacet, PersonaGeneratorOptions } from './memory/persona.ts';
173
+ export { MemoryCheckpoint, getMemoryCheckpoint, resetMemoryCheckpoint } from './memory/checkpoint.ts';
174
+ export type { CheckpointData } from './memory/checkpoint.ts';
175
+ export { MemoryRecallEngine, formatRecallForPrompt } from './memory/recall.ts';
176
+ export type { RecallResult, ScoredMemoryEntry, RecallOptions } from './memory/recall.ts';
177
+ export { MemoryPipeline } from './memory/pipeline.ts';
178
+ export type { PipelineOptions, PipelineResult } from './memory/pipeline.ts';
@@ -0,0 +1,412 @@
1
+ /**
2
+ * @file Anthropic Claude LLM Provider
3
+ * @description 实现 Anthropic Messages API,支持 Claude 系列模型,
4
+ * 内部自动完成 OpenAI 格式 ↔ Anthropic 格式的双向转换
5
+ */
6
+
7
+ import type { LLMProvider, ModelConfig, ModelInfo, ChatParams, ChatResponse, ChatChunk } from './types.ts';
8
+ import type { Message, ToolCall } from '../types.ts';
9
+
10
+ // ---- Anthropic API 内部类型 ----
11
+
12
+ interface AnthropicContentBlock {
13
+ type: 'text' | 'tool_use' | 'tool_result';
14
+ text?: string;
15
+ id?: string;
16
+ name?: string;
17
+ input?: Record<string, unknown>;
18
+ tool_use_id?: string;
19
+ content?: string;
20
+ }
21
+
22
+ interface AnthropicMessage {
23
+ role: 'user' | 'assistant';
24
+ content: string | AnthropicContentBlock[];
25
+ }
26
+
27
+ interface AnthropicTool {
28
+ name: string;
29
+ description?: string;
30
+ input_schema: Record<string, unknown>;
31
+ }
32
+
33
+ // ---- 上下文窗口映射 ----
34
+
35
+ const CONTEXT_WINDOWS: Record<string, number> = {
36
+ 'claude-sonnet-4': 200000,
37
+ 'claude-opus-4': 200000,
38
+ 'claude-3-5-sonnet': 200000,
39
+ 'claude-3-5-haiku': 200000,
40
+ 'claude-3-opus': 200000,
41
+ 'claude-3-sonnet': 200000,
42
+ 'claude-3-haiku': 200000,
43
+ };
44
+
45
+ function getContextWindow(model: string): number {
46
+ for (const [prefix, ctx] of Object.entries(CONTEXT_WINDOWS)) {
47
+ if (model.startsWith(prefix)) return ctx;
48
+ }
49
+ return 200000; // 默认 200k
50
+ }
51
+
52
+ // ---- Provider 实现 ----
53
+
54
+ export class AnthropicProvider implements LLMProvider {
55
+ private config: Required<Pick<ModelConfig, 'provider' | 'model'>> & ModelConfig;
56
+
57
+ constructor(config: ModelConfig) {
58
+ this.config = {
59
+ ...config,
60
+ provider: 'anthropic',
61
+ model: config.model || 'claude-sonnet-4-20250514',
62
+ baseUrl: config.baseUrl || 'https://api.anthropic.com',
63
+ temperature: config.temperature ?? 0.7,
64
+ maxTokens: (typeof config.maxTokens === 'number' && config.maxTokens > 0) ? config.maxTokens : 4096,
65
+ timeout: config.timeout ?? 120000,
66
+ };
67
+ }
68
+
69
+ getModel(): ModelInfo {
70
+ return {
71
+ provider: this.config.provider,
72
+ model: this.config.model,
73
+ contextWindow: getContextWindow(this.config.model),
74
+ supportsTools: true,
75
+ supportsStreaming: true,
76
+ };
77
+ }
78
+
79
+ async ping(): Promise<boolean> {
80
+ try {
81
+ const res = await this.doRequest(
82
+ {
83
+ model: this.config.model,
84
+ max_tokens: 5,
85
+ messages: [{ role: 'user', content: 'ping' }],
86
+ },
87
+ );
88
+ return res.ok;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ async chat(params: ChatParams): Promise<ChatResponse> {
95
+ const body = this.buildRequestBody(params);
96
+ const res = await this.doRequest(body, params.abortSignal);
97
+
98
+ if (!res.ok) {
99
+ const errText = await res.text().catch(() => 'Unknown error');
100
+ throw new Error(`Anthropic API error ${res.status}: ${errText}`);
101
+ }
102
+
103
+ const data = await res.json();
104
+ return this.parseResponse(data);
105
+ }
106
+
107
+ async *chatStream(params: ChatParams): AsyncGenerator<ChatChunk> {
108
+ const body = this.buildRequestBody(params);
109
+ body.stream = true;
110
+
111
+ const res = await this.doRequest(body, params.abortSignal);
112
+
113
+ if (!res.ok) {
114
+ const errText = await res.text().catch(() => 'Unknown error');
115
+ throw new Error(`Anthropic API error ${res.status}: ${errText}`);
116
+ }
117
+
118
+ const reader = res.body?.getReader();
119
+ if (!reader) throw new Error('No response body');
120
+
121
+ const decoder = new TextDecoder();
122
+ let buffer = '';
123
+
124
+ // 流式状态
125
+ let currentToolCallId = '';
126
+ let currentToolCallName = '';
127
+ let toolCallArgsBuffer = '';
128
+ let toolCallIndex = 0; // 全局递增计数器,为每个 tool_use block 分配唯一 index
129
+ let currentToolCallIndex = 0; // 当前正在处理的 tool_use block 的 index
130
+ // Usage 跟踪
131
+ let inputTokens = 0;
132
+ let outputTokens = 0;
133
+
134
+ try {
135
+ while (true) {
136
+ const { done, value } = await reader.read();
137
+ if (done) break;
138
+
139
+ buffer += decoder.decode(value, { stream: true });
140
+ const lines = buffer.split('\n');
141
+ buffer = lines.pop() || '';
142
+
143
+ for (const line of lines) {
144
+ const trimmed = line.trim();
145
+ if (!trimmed || !trimmed.startsWith('data: ')) continue;
146
+
147
+ try {
148
+ const json = JSON.parse(trimmed.slice(6));
149
+
150
+ switch (json.type) {
151
+ case 'message_start': {
152
+ // 提取 input tokens
153
+ const usage = json.message?.usage;
154
+ if (usage) {
155
+ inputTokens = usage.input_tokens || 0;
156
+ }
157
+ break;
158
+ }
159
+
160
+ case 'content_block_start': {
161
+ const block = json.content_block;
162
+ if (block?.type === 'tool_use') {
163
+ currentToolCallId = block.id || '';
164
+ currentToolCallName = block.name || '';
165
+ currentToolCallIndex = toolCallIndex++;
166
+ toolCallArgsBuffer = '';
167
+ }
168
+ break;
169
+ }
170
+
171
+ case 'content_block_delta': {
172
+ const delta = json.delta;
173
+ if (delta?.type === 'text_delta') {
174
+ yield {
175
+ delta: { content: delta.text, role: 'assistant' },
176
+ };
177
+ } else if (delta?.type === 'input_json_delta') {
178
+ toolCallArgsBuffer += delta.partial_json || '';
179
+ }
180
+ break;
181
+ }
182
+
183
+ case 'content_block_stop': {
184
+ if (currentToolCallId) {
185
+ // 工具调用完成,输出完整的 tool_call(必须携带 index,否则 LLMCaller 会将多个 tool_call 合并到 index=0)
186
+ const toolCalls: (ToolCall & { index: number })[] = [{
187
+ index: currentToolCallIndex,
188
+ id: currentToolCallId,
189
+ type: 'function',
190
+ function: {
191
+ name: currentToolCallName,
192
+ arguments: toolCallArgsBuffer || '{}',
193
+ },
194
+ }];
195
+ yield {
196
+ delta: { role: 'assistant', tool_calls: toolCalls },
197
+ finish_reason: 'tool_calls',
198
+ };
199
+ currentToolCallId = '';
200
+ currentToolCallName = '';
201
+ toolCallArgsBuffer = '';
202
+ }
203
+ break;
204
+ }
205
+
206
+ case 'message_delta': {
207
+ const stopReason = json.delta?.stop_reason;
208
+ // 提取 output tokens
209
+ const usage = json.usage;
210
+ if (usage) {
211
+ outputTokens = usage.output_tokens || 0;
212
+ }
213
+ if (stopReason) {
214
+ yield {
215
+ delta: {},
216
+ finish_reason: stopReason === 'tool_use' ? 'tool_calls' : stopReason,
217
+ usage: {
218
+ promptTokens: inputTokens,
219
+ completionTokens: outputTokens,
220
+ totalTokens: inputTokens + outputTokens,
221
+ },
222
+ };
223
+ }
224
+ break;
225
+ }
226
+ }
227
+ } catch {
228
+ // skip malformed chunks
229
+ }
230
+ }
231
+ }
232
+ } finally {
233
+ reader.releaseLock();
234
+ }
235
+ }
236
+
237
+ /** 更新配置 */
238
+ updateConfig(updates: Partial<ModelConfig>): void {
239
+ Object.assign(this.config, updates);
240
+ }
241
+
242
+ // ---- 内部方法 ----
243
+
244
+ /**
245
+ * 将 OpenAI 格式消息转换为 Anthropic 格式
246
+ * - system 消息提取为单独的 system 字段
247
+ * - tool 消息转换为 user 消息中的 tool_result 块
248
+ * - assistant 的 tool_calls 转换为 tool_use 块
249
+ */
250
+ private convertMessages(messages: Message[]): {
251
+ system?: string;
252
+ messages: AnthropicMessage[];
253
+ } {
254
+ let system: string | undefined;
255
+ const result: AnthropicMessage[] = [];
256
+
257
+ for (const msg of messages) {
258
+ switch (msg.role) {
259
+ case 'system':
260
+ system = (system ? system + '\n\n' : '') + (msg.content || '');
261
+ break;
262
+
263
+ case 'user':
264
+ result.push({ role: 'user', content: msg.content || '' });
265
+ break;
266
+
267
+ case 'assistant': {
268
+ if (msg.tool_calls?.length) {
269
+ // assistant 带 tool_calls → text + tool_use blocks
270
+ const blocks: AnthropicContentBlock[] = [];
271
+ if (msg.content) {
272
+ blocks.push({ type: 'text', text: msg.content });
273
+ }
274
+ for (const tc of msg.tool_calls) {
275
+ let input: Record<string, unknown>;
276
+ try {
277
+ input = JSON.parse(tc.function.arguments || '{}');
278
+ } catch {
279
+ // tool_call 参数 JSON 截断或格式错误时降级为空对象,避免整次 LLM 调用失败
280
+ console.warn(`[AnthropicProvider] Failed to parse tool_call arguments for '${tc.function.name}', using empty object. Raw: ${(tc.function.arguments || '').slice(0, 100)}`);
281
+ input = { _raw_arguments: tc.function.arguments || '' };
282
+ }
283
+ blocks.push({
284
+ type: 'tool_use',
285
+ id: tc.id,
286
+ name: tc.function.name,
287
+ input,
288
+ });
289
+ }
290
+ result.push({ role: 'assistant', content: blocks });
291
+ } else {
292
+ result.push({ role: 'assistant', content: msg.content || '' });
293
+ }
294
+ break;
295
+ }
296
+
297
+ case 'tool': {
298
+ // tool 消息 → user 消息中的 tool_result 块
299
+ result.push({
300
+ role: 'user',
301
+ content: [{
302
+ type: 'tool_result',
303
+ tool_use_id: msg.tool_call_id,
304
+ content: msg.content || '',
305
+ }],
306
+ });
307
+ break;
308
+ }
309
+ }
310
+ }
311
+
312
+ return { system, messages: result };
313
+ }
314
+
315
+ private convertTools(params: ChatParams): AnthropicTool[] | undefined {
316
+ if (!params.tools?.length) return undefined;
317
+ return params.tools.map(t => ({
318
+ name: t.function.name,
319
+ description: t.function.description,
320
+ input_schema: t.function.parameters || { type: 'object', properties: {} },
321
+ }));
322
+ }
323
+
324
+ private buildRequestBody(params: ChatParams): Record<string, any> {
325
+ const { system, messages } = this.convertMessages(params.messages);
326
+ const tools = this.convertTools(params);
327
+
328
+ const body: Record<string, any> = {
329
+ model: this.config.model,
330
+ max_tokens: (typeof params.maxTokens === 'number' && params.maxTokens > 0)
331
+ ? params.maxTokens
332
+ : (typeof this.config.maxTokens === 'number' && this.config.maxTokens > 0)
333
+ ? this.config.maxTokens
334
+ : 4096,
335
+ messages,
336
+ temperature: params.temperature ?? this.config.temperature,
337
+ };
338
+
339
+ if (system) body.system = system;
340
+ if (tools?.length) body.tools = tools;
341
+
342
+ return body;
343
+ }
344
+
345
+ private parseResponse(data: any): ChatResponse {
346
+ const content: string[] = [];
347
+ const toolCalls: ToolCall[] = [];
348
+
349
+ for (const block of data.content || []) {
350
+ if (block.type === 'text') {
351
+ content.push(block.text || '');
352
+ } else if (block.type === 'tool_use') {
353
+ toolCalls.push({
354
+ id: block.id,
355
+ type: 'function',
356
+ function: {
357
+ name: block.name,
358
+ arguments: JSON.stringify(block.input || {}),
359
+ },
360
+ });
361
+ }
362
+ }
363
+
364
+ const message: Message = {
365
+ role: 'assistant',
366
+ content: content.join('') || null,
367
+ ...(toolCalls.length ? { tool_calls: toolCalls } : {}),
368
+ };
369
+
370
+ return {
371
+ choices: [{
372
+ message,
373
+ finish_reason: data.stop_reason === 'tool_use' ? 'tool_calls' : (data.stop_reason || 'stop'),
374
+ }],
375
+ usage: data.usage
376
+ ? {
377
+ promptTokens: data.usage.input_tokens || 0,
378
+ completionTokens: data.usage.output_tokens || 0,
379
+ totalTokens: (data.usage.input_tokens || 0) + (data.usage.output_tokens || 0),
380
+ }
381
+ : undefined,
382
+ };
383
+ }
384
+
385
+ private async doRequest(body: Record<string, any>, signal?: AbortSignal): Promise<Response> {
386
+ const url = `${this.config.baseUrl!.replace(/\/$/, '')}/v1/messages`;
387
+
388
+ const controller = new AbortController();
389
+ const timeout = setTimeout(() => controller.abort(), this.config.timeout);
390
+
391
+ if (signal) {
392
+ if (signal.aborted) controller.abort();
393
+ else signal.addEventListener('abort', () => controller.abort(), { once: true });
394
+ }
395
+
396
+ try {
397
+ return await fetch(url, {
398
+ method: 'POST',
399
+ headers: {
400
+ 'Content-Type': 'application/json',
401
+ 'anthropic-version': '2023-06-01',
402
+ ...(this.config.apiKey ? { 'x-api-key': this.config.apiKey } : {}),
403
+ },
404
+ body: JSON.stringify(body),
405
+ signal: controller.signal,
406
+ ...(this.config.proxy ? { dispatcher: this.config.proxy } : {}),
407
+ } as RequestInit);
408
+ } finally {
409
+ clearTimeout(timeout);
410
+ }
411
+ }
412
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @file LLM Provider 工厂
3
+ * @description 根据 ModelConfig.provider 自动创建对应的 LLMProvider 实例
4
+ * 支持 'openai' | 'anthropic' | 'ollama' 及任意 OpenAI 兼容供应商
5
+ */
6
+
7
+ import type { ModelConfig, LLMProvider } from './types.ts';
8
+ import { OpenAIProvider } from './openai-provider.ts';
9
+ import { AnthropicProvider } from './anthropic-provider.ts';
10
+ import { OllamaProvider } from './ollama-provider.ts';
11
+
12
+ /** 已注册的供应商名称集合 */
13
+ export const BUILTIN_PROVIDERS = ['openai', 'anthropic', 'ollama'] as const;
14
+ export type BuiltinProvider = (typeof BUILTIN_PROVIDERS)[number];
15
+
16
+ /**
17
+ * 根据 config.provider 创建对应的 LLMProvider
18
+ *
19
+ * - 'openai' → OpenAIProvider
20
+ * - 'anthropic' → AnthropicProvider
21
+ * - 'ollama' → OllamaProvider
22
+ * - 其他任意字符串 → OpenAIProvider(OpenAI 兼容端点,如 DeepSeek / vLLM / Together 等)
23
+ */
24
+ export function createLLMProvider(config: ModelConfig): LLMProvider {
25
+ switch (config.provider?.toLowerCase()) {
26
+ case 'anthropic':
27
+ return new AnthropicProvider(config);
28
+ case 'ollama':
29
+ return new OllamaProvider(config);
30
+ case 'openai':
31
+ default:
32
+ return new OpenAIProvider(config);
33
+ }
34
+ }