feat(ai): upgrade pi-ai from @mariozechner 0.54 to @earendil-works 0.74

- Replace @mariozechner/pi-ai and pi-agent-core with @earendil-works scope
- Adapt to AgentTool.execute() params type change (Record → unknown)
- Migrate Agent constructor to use initialState for systemPrompt/tools/messages
- Update onPayload callback signature to include model parameter
- Filter finalMessages with isPiMessage() for new AgentMessage union type
- Use state.errorMessage instead of deprecated state.error

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
diguaandCursor authored and digua committed 2026-05-26 23:19:47 +08:00
1 parent 0ba9ae04a6
commit 6c3d36e7df
17 files changed
+388 -1211

No files matched your search

+3 -2
View File
@@ -2,6 +2,7 @@
"name": "chatlab-cli",
"version": "0.0.0",
"description": "ChatLab CLI & Service - Chat history analysis tool",
"type": "module",
"bin": {
"chatlab": "bin/chatlab.mjs"
},
@@ -46,8 +47,8 @@
"dependencies": {
"@fastify/multipart": "^10.0.0",
"@fastify/static": "^9.1.3",
"@mariozechner/pi-agent-core": "0.54.2",
"@mariozechner/pi-ai": "0.54.2",
"@earendil-works/pi-agent-core": "0.74.2",
"@earendil-works/pi-ai": "0.74.2",
"@modelcontextprotocol/sdk": "^1.12.1",
"better-sqlite3": "^12.4.6",
"commander": "^13.1.0",
+4 -3
View File
@@ -1,7 +1,7 @@
/**
* 工具适配层
*
* 将 @openchatlab/tools 的 ToolDefinition 适配为 @mariozechner/pi-agent-core 的 AgentTool 格式。
* 将 @openchatlab/tools 的 ToolDefinition 适配为 @earendil-works/pi-agent-core 的 AgentTool 格式。
* 消息类工具返回 rawMessages 时自动执行预处理管道(清洗、去噪、脱敏、截断、格式化)。
*/
@@ -63,7 +63,8 @@ export function adaptToolsForAgent(
label: tool.name,
description: tool.description,
parameters: convertJsonSchemaToParameters(tool.inputSchema) as any,
async execute(_toolCallId: string, params: Record<string, unknown>): Promise<AgentToolResult<unknown>> {
async execute(_toolCallId: string, params: unknown): Promise<AgentToolResult<unknown>> {
const toolParams = (params && typeof params === 'object' ? params : {}) as Record<string, unknown>
const ctx = getContext()
const execCtx: ToolExecutionContext = {
db: ctx.db,
@@ -73,7 +74,7 @@ export function adaptToolsForAgent(
segmentText: (texts, locale, options) => batchSegmentWithFrequency(texts, locale as any, options as any),
}
try {
const result = await tool.handler(params, execCtx)
const result = await tool.handler(toolParams, execCtx)
if (result.rawMessages && result.rawMessages.length > 0) {
const pipelineResult = applyPreprocessingPipeline({
+2 -2
View File
@@ -110,9 +110,9 @@ export class Agent {
const errorCapturingStreamFn: typeof streamSimple = (model, context, options) => {
return streamSimple(model, context, {
...options,
onPayload: (payload) => {
onPayload: (payload, model) => {
lastRequestPayload = payload
options?.onPayload?.(payload)
return options?.onPayload?.(payload, model)
},
})
}
@@ -51,10 +51,11 @@ export function adaptSharedTool(tool: ToolDefinition, options: AdaptOptions): To
label: tool.name,
description: `ai.tools.${tool.name}.desc`,
parameters: schema as any,
async execute(_toolCallId: string, params: Record<string, unknown>): Promise<AgentToolResult<unknown>> {
async execute(_toolCallId: string, params: unknown): Promise<AgentToolResult<unknown>> {
const toolParams = (params && typeof params === 'object' ? params : {}) as Record<string, unknown>
const execCtx = buildExecutionContext(context)
try {
const result = await tool.handler(params, execCtx)
const result = await tool.handler(toolParams, execCtx)
if (result.rawMessages && result.rawMessages.length > 0) {
const baseData = (typeof result.data === 'object' && result.data !== null ? result.data : {}) as Record<
+3 -2
View File
@@ -3,11 +3,12 @@
"version": "0.0.0",
"private": true,
"description": "ChatLab Node.js 运行时适配器:better-sqlite3、文件系统、路径管理",
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"dependencies": {
"@mariozechner/pi-agent-core": "0.54.2",
"@mariozechner/pi-ai": "0.54.2",
"@earendil-works/pi-agent-core": "0.74.2",
"@earendil-works/pi-ai": "0.74.2",
"@openchatlab/core": "workspace:*",
"better-sqlite3": "^12.4.6",
"gray-matter": "^4.0.3",
@@ -27,7 +27,7 @@ export interface ActivateSkillTool {
parameters: Record<string, unknown>
execute: (
toolCallId: string,
params: { skill_id: string },
params: unknown,
signal?: AbortSignal,
onUpdate?: unknown
) => Promise<ActivateSkillToolResult>
@@ -53,12 +53,14 @@ export function createActivateSkillTool(options: ActivateSkillToolOptions): Acti
},
required: ['skill_id'],
},
execute: async (_toolCallId: string, params: { skill_id: string }) => {
const skill: SkillDef | null = getSkillConfig(params.skill_id)
execute: async (_toolCallId: string, params: unknown) => {
const toolParams = (params && typeof params === 'object' ? params : {}) as { skill_id?: string }
const skillId = toolParams.skill_id || ''
const skill: SkillDef | null = getSkillConfig(skillId)
if (!skill) {
return {
content: [{ type: 'text' as const, text: isZh ? '技能不存在' : 'Skill not found' }],
details: { skillId: params.skill_id, found: false },
details: { skillId, found: false },
}
}
@@ -68,7 +70,7 @@ export function createActivateSkillTool(options: ActivateSkillToolOptions): Acti
: `This skill is only applicable to ${skill.chatScope === 'group' ? 'group chat' : 'private chat'} scenarios`
return {
content: [{ type: 'text' as const, text: scopeMsg }],
details: { skillId: params.skill_id, found: true, applicable: false },
details: { skillId, found: true, applicable: false },
}
}
@@ -80,7 +82,7 @@ export function createActivateSkillTool(options: ActivateSkillToolOptions): Acti
: `Current assistant lacks tools required by this skill: ${missing.join(', ')}`
return {
content: [{ type: 'text' as const, text: msg }],
details: { skillId: params.skill_id, found: true, applicable: false, missingTools: missing },
details: { skillId, found: true, applicable: false, missingTools: missing },
}
}
}
@@ -91,7 +93,7 @@ export function createActivateSkillTool(options: ActivateSkillToolOptions): Acti
return {
content: [{ type: 'text' as const, text: `${skill.prompt}${actionPrompt}` }],
details: { skillId: params.skill_id, found: true, applicable: true },
details: { skillId, found: true, applicable: true },
}
},
}
+13 -10
View File
@@ -5,13 +5,13 @@
* Server 和 Electron 通过 AgentCoreOptions DI 注入平台差异。
*/
import { Agent as PiAgentCore } from '@mariozechner/pi-agent-core'
import type { AgentEvent as PiAgentEvent } from '@mariozechner/pi-agent-core'
import { Agent as PiAgentCore } from '@earendil-works/pi-agent-core'
import type { AgentEvent as PiAgentEvent, AgentMessage as PiAgentMessage } from '@earendil-works/pi-agent-core'
import {
type Message as PiMessage,
type Usage as PiUsage,
streamSimple as defaultStreamSimple,
} from '@mariozechner/pi-ai'
} from '@earendil-works/pi-ai'
import { StreamingThinkTagParser, needsStreamingThinkParsing } from '@openchatlab/core'
import type { AgentCoreOptions, AgentCoreResult, AgentTokenUsage, SimpleHistoryMessage } from './types'
@@ -49,6 +49,10 @@ function toPiHistoryMessages(messages: SimpleHistoryMessage[]): PiMessage[] {
})
}
function isPiMessage(message: PiAgentMessage): message is PiMessage {
return message.role === 'user' || message.role === 'assistant' || message.role === 'toolResult'
}
export async function runAgentCore(options: AgentCoreOptions): Promise<AgentCoreResult> {
const {
piModel,
@@ -84,8 +88,11 @@ export async function runAgentCore(options: AgentCoreOptions): Promise<AgentCore
const coreAgent = new PiAgentCore({
initialState: {
systemPrompt,
model: piModel,
thinkingLevel: piModel.reasoning ? 'medium' : 'off',
tools: maxToolRounds > 0 ? tools : [],
messages: toPiHistoryMessages(history),
},
getApiKey: () => apiKey,
streamFn: resolvedStreamFn,
@@ -98,10 +105,6 @@ export async function runAgentCore(options: AgentCoreOptions): Promise<AgentCore
},
})
coreAgent.setSystemPrompt(systemPrompt)
coreAgent.setTools(maxToolRounds > 0 ? tools : [])
coreAgent.replaceMessages(toPiHistoryMessages(history))
let hasReachedToolRoundLimit = false
const thinkingStartTime = new Map<number, number>()
@@ -167,7 +170,7 @@ export async function runAgentCore(options: AgentCoreOptions): Promise<AgentCore
toolRounds += 1
if (!hasReachedToolRoundLimit && maxToolRounds > 0 && toolRounds >= maxToolRounds) {
hasReachedToolRoundLimit = true
coreAgent.setTools([])
coreAgent.state.tools = []
coreAgent.steer({
role: 'user',
content: [{ type: 'text', text: steerMessage }],
@@ -211,8 +214,8 @@ export async function runAgentCore(options: AgentCoreOptions): Promise<AgentCore
return {
usage: totalUsage,
error: coreAgent.state.error || undefined,
finalMessages: [...coreAgent.state.messages],
error: coreAgent.state.errorMessage || undefined,
finalMessages: coreAgent.state.messages.filter(isPiMessage),
toolsUsed: [...toolsUsed],
toolRounds,
}
@@ -5,7 +5,7 @@
* estimates context tokens. Platform-agnostic via generic chunk type.
*/
import type { Message as PiMessage } from '@mariozechner/pi-ai'
import type { Message as PiMessage } from '@earendil-works/pi-ai'
import type { AgentCoreEvent } from './types'
// ==================== Shared types ====================
+2 -2
View File
@@ -5,8 +5,8 @@
* 供 Server 和 Electron 两端通过 DI 适配。
*/
import type { AgentTool } from '@mariozechner/pi-agent-core'
import type { Model, Api, Message } from '@mariozechner/pi-ai'
import type { AgentTool } from '@earendil-works/pi-agent-core'
import type { Model, Api, Message } from '@earendil-works/pi-ai'
export interface AgentTokenUsage {
promptTokens: number
@@ -3,7 +3,7 @@ import {
type Model as PiModel,
type Api as PiApi,
type TextContent as PiTextContent,
} from '@mariozechner/pi-ai'
} from '@earendil-works/pi-ai'
import type { CompressionLlmAdapter } from './types'
export interface CreateCompressionLlmAdapterOptions {
+5 -5
View File
@@ -134,15 +134,15 @@ export type { PiModelConfig, BuildPiModelOptions } from './llm-builder'
export { fetchRemoteModels, validateApiKey } from './remote-api'
export type { RemoteModel, FetchRemoteModelsResult, RemoteApiOptions } from './remote-api'
// Re-exports from @mariozechner/pi-agent-core
export type { AgentTool, AgentToolResult } from '@mariozechner/pi-agent-core'
// Re-exports from @earendil-works/pi-agent-core
export type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core'
// LLM simple streaming
export { runSimpleLlmStream } from './llm-stream'
export type { LlmStreamChunk, RunSimpleLlmStreamOptions } from './llm-stream'
// Re-exports from @mariozechner/pi-ai
export { Type, completeSimple, streamSimple } from '@mariozechner/pi-ai'
// Re-exports from @earendil-works/pi-ai
export { Type, completeSimple, streamSimple } from '@earendil-works/pi-ai'
export type {
Model as PiModel,
Api as PiApi,
@@ -150,7 +150,7 @@ export type {
Usage as PiUsage,
TextContent as PiTextContent,
AssistantMessage as PiAssistantMessage,
} from '@mariozechner/pi-ai'
} from '@earendil-works/pi-ai'
// ==================== RAG ====================
export {
+2 -2
View File
@@ -1,13 +1,13 @@
/**
* Shared PiModel builder — translates an AIServiceConfig-like
* object into a PiModel instance for @mariozechner/pi-ai.
* object into a PiModel instance for @earendil-works/pi-ai.
*
* Used by both Electron and Server to eliminate duplicated
* URL normalization, apiFormat mapping, and model construction.
*/
import { BUILTIN_PROVIDERS, getBuiltinModelsByProvider, BUILTIN_MODELS, type ModelDefinition } from '@openchatlab/core'
import type { Model as PiModel, Api as PiApi } from '@mariozechner/pi-ai'
import type { Model as PiModel, Api as PiApi } from '@earendil-works/pi-ai'
export interface PiModelConfig {
provider: string
+2 -2
View File
@@ -5,8 +5,8 @@
* event mapping, and error handling.
*/
import { streamSimple } from '@mariozechner/pi-ai'
import type { Model as PiModel, Api as PiApi, Message as PiMessage } from '@mariozechner/pi-ai'
import { streamSimple } from '@earendil-works/pi-ai'
import type { Model as PiModel, Api as PiApi, Message as PiMessage } from '@earendil-works/pi-ai'
export interface LlmStreamChunk {
content: string
@@ -16,7 +16,7 @@ import { getEmbeddingService } from '../embedding'
import { getVectorStore } from '../store'
import { getSessionChunks } from '../chunking'
import { loadRAGConfig } from '../config'
import { completeSimple, type TextContent } from '@mariozechner/pi-ai'
import { completeSimple, type TextContent } from '@earendil-works/pi-ai'
import { buildPiModel, type PiModelConfig } from '../../llm-builder'
/**
+2 -2
View File
@@ -6,9 +6,9 @@
*/
import { BUILTIN_PROVIDERS } from '@openchatlab/core'
import { completeSimple } from '@mariozechner/pi-ai'
import { completeSimple } from '@earendil-works/pi-ai'
import { buildPiModel, normalizeOpenAICompatibleBaseUrl, type PiModelConfig } from './llm-builder'
import type { Model as PiModel, Api as PiApi } from '@mariozechner/pi-ai'
import type { Model as PiModel, Api as PiApi } from '@earendil-works/pi-ai'
export interface RemoteModel {
id: string
+1 -1
View File
@@ -255,7 +255,7 @@ export {
} from './merger/temp-db'
export type { TempDbMeta, ExportedSession } from './merger/temp-db'
// Re-exports: @mariozechner/pi-agent-core & @mariozechner/pi-ai
// Re-exports: @earendil-works/pi-agent-core & @earendil-works/pi-ai
export type { AgentTool, AgentToolResult } from './ai'
export { Type, completeSimple, streamSimple, runSimpleLlmStream } from './ai'
export type { LlmStreamChunk, RunSimpleLlmStreamOptions } from './ai'
+334 -1166
View File
File diff suppressed because it is too large. Load diff