@dpml/agent
Version:
Agent implementation for DPML
1 lines • 79.7 kB
Source Map (JSON)
{"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.4.0_@swc+core@1.11.24_postcss@8.5.3_typescript@5.8.2_yaml@2.7.1/node_modules/tsup/assets/esm_shims.js","../../../node_modules/.pnpm/dotenv@16.5.0/node_modules/dotenv/package.json","../../../node_modules/.pnpm/dotenv@16.5.0/node_modules/dotenv/lib/main.js","../src/index.ts","../src/config/index.ts","../src/config/schema.ts","../src/config/transformers.ts","../src/config/cli.ts","../src/api/agent.ts","../src/core/agentService.ts","../src/types/index.ts","../src/types/errors.ts","../src/core/AgentRunner.ts","../src/core/llm/llmFactory.ts","../src/core/llm/AnthropicClient.ts","../src/core/llm/OpenAIClient.ts","../src/core/session/InMemoryAgentSession.ts","../src/api/agentenv.ts","../src/core/agentenv/agentenvCore.ts","../src/core/agentenv/constants.ts","../src/api/index.ts","../src/bin.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport { fileURLToPath } from 'url'\nimport path from 'path'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","{\n \"name\": \"dotenv\",\n \"version\": \"16.5.0\",\n \"description\": \"Loads environment variables from .env file\",\n \"main\": \"lib/main.js\",\n \"types\": \"lib/main.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./lib/main.d.ts\",\n \"require\": \"./lib/main.js\",\n \"default\": \"./lib/main.js\"\n },\n \"./config\": \"./config.js\",\n \"./config.js\": \"./config.js\",\n \"./lib/env-options\": \"./lib/env-options.js\",\n \"./lib/env-options.js\": \"./lib/env-options.js\",\n \"./lib/cli-options\": \"./lib/cli-options.js\",\n \"./lib/cli-options.js\": \"./lib/cli-options.js\",\n \"./package.json\": \"./package.json\"\n },\n \"scripts\": {\n \"dts-check\": \"tsc --project tests/types/tsconfig.json\",\n \"lint\": \"standard\",\n \"pretest\": \"npm run lint && npm run dts-check\",\n \"test\": \"tap run --allow-empty-coverage --disable-coverage --timeout=60000\",\n \"test:coverage\": \"tap run --show-full-coverage --timeout=60000 --coverage-report=lcov\",\n \"prerelease\": \"npm test\",\n \"release\": \"standard-version\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git://github.com/motdotla/dotenv.git\"\n },\n \"homepage\": \"https://github.com/motdotla/dotenv#readme\",\n \"funding\": \"https://dotenvx.com\",\n \"keywords\": [\n \"dotenv\",\n \"env\",\n \".env\",\n \"environment\",\n \"variables\",\n \"config\",\n \"settings\"\n ],\n \"readmeFilename\": \"README.md\",\n \"license\": \"BSD-2-Clause\",\n \"devDependencies\": {\n \"@types/node\": \"^18.11.3\",\n \"decache\": \"^4.6.2\",\n \"sinon\": \"^14.0.1\",\n \"standard\": \"^17.0.0\",\n \"standard-version\": \"^9.5.0\",\n \"tap\": \"^19.2.0\",\n \"typescript\": \"^4.8.4\"\n },\n \"engines\": {\n \"node\": \">=12\"\n },\n \"browser\": {\n \"fs\": false\n }\n}\n","const fs = require('fs')\nconst path = require('path')\nconst os = require('os')\nconst crypto = require('crypto')\nconst packageJson = require('../package.json')\n\nconst version = packageJson.version\n\nconst LINE = /(?:^|^)\\s*(?:export\\s+)?([\\w.-]+)(?:\\s*=\\s*?|:\\s+?)(\\s*'(?:\\\\'|[^'])*'|\\s*\"(?:\\\\\"|[^\"])*\"|\\s*`(?:\\\\`|[^`])*`|[^#\\r\\n]+)?\\s*(?:#.*)?(?:$|$)/mg\n\n// Parse src into an Object\nfunction parse (src) {\n const obj = {}\n\n // Convert buffer to string\n let lines = src.toString()\n\n // Convert line breaks to same format\n lines = lines.replace(/\\r\\n?/mg, '\\n')\n\n let match\n while ((match = LINE.exec(lines)) != null) {\n const key = match[1]\n\n // Default undefined or null to empty string\n let value = (match[2] || '')\n\n // Remove whitespace\n value = value.trim()\n\n // Check if double quoted\n const maybeQuote = value[0]\n\n // Remove surrounding quotes\n value = value.replace(/^(['\"`])([\\s\\S]*)\\1$/mg, '$2')\n\n // Expand newlines if double quoted\n if (maybeQuote === '\"') {\n value = value.replace(/\\\\n/g, '\\n')\n value = value.replace(/\\\\r/g, '\\r')\n }\n\n // Add to object\n obj[key] = value\n }\n\n return obj\n}\n\nfunction _parseVault (options) {\n const vaultPath = _vaultPath(options)\n\n // Parse .env.vault\n const result = DotenvModule.configDotenv({ path: vaultPath })\n if (!result.parsed) {\n const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`)\n err.code = 'MISSING_DATA'\n throw err\n }\n\n // handle scenario for comma separated keys - for use with key rotation\n // example: DOTENV_KEY=\"dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod\"\n const keys = _dotenvKey(options).split(',')\n const length = keys.length\n\n let decrypted\n for (let i = 0; i < length; i++) {\n try {\n // Get full key\n const key = keys[i].trim()\n\n // Get instructions for decrypt\n const attrs = _instructions(result, key)\n\n // Decrypt\n decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key)\n\n break\n } catch (error) {\n // last key\n if (i + 1 >= length) {\n throw error\n }\n // try next key\n }\n }\n\n // Parse decrypted .env string\n return DotenvModule.parse(decrypted)\n}\n\nfunction _warn (message) {\n console.log(`[dotenv@${version}][WARN] ${message}`)\n}\n\nfunction _debug (message) {\n console.log(`[dotenv@${version}][DEBUG] ${message}`)\n}\n\nfunction _dotenvKey (options) {\n // prioritize developer directly setting options.DOTENV_KEY\n if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {\n return options.DOTENV_KEY\n }\n\n // secondary infra already contains a DOTENV_KEY environment variable\n if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {\n return process.env.DOTENV_KEY\n }\n\n // fallback to empty string\n return ''\n}\n\nfunction _instructions (result, dotenvKey) {\n // Parse DOTENV_KEY. Format is a URI\n let uri\n try {\n uri = new URL(dotenvKey)\n } catch (error) {\n if (error.code === 'ERR_INVALID_URL') {\n const err = new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n }\n\n throw error\n }\n\n // Get decrypt key\n const key = uri.password\n if (!key) {\n const err = new Error('INVALID_DOTENV_KEY: Missing key part')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n }\n\n // Get environment\n const environment = uri.searchParams.get('environment')\n if (!environment) {\n const err = new Error('INVALID_DOTENV_KEY: Missing environment part')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n }\n\n // Get ciphertext payload\n const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`\n const ciphertext = result.parsed[environmentKey] // DOTENV_VAULT_PRODUCTION\n if (!ciphertext) {\n const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`)\n err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT'\n throw err\n }\n\n return { ciphertext, key }\n}\n\nfunction _vaultPath (options) {\n let possibleVaultPath = null\n\n if (options && options.path && options.path.length > 0) {\n if (Array.isArray(options.path)) {\n for (const filepath of options.path) {\n if (fs.existsSync(filepath)) {\n possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`\n }\n }\n } else {\n possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`\n }\n } else {\n possibleVaultPath = path.resolve(process.cwd(), '.env.vault')\n }\n\n if (fs.existsSync(possibleVaultPath)) {\n return possibleVaultPath\n }\n\n return null\n}\n\nfunction _resolveHome (envPath) {\n return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath\n}\n\nfunction _configVault (options) {\n const debug = Boolean(options && options.debug)\n if (debug) {\n _debug('Loading env from encrypted .env.vault')\n }\n\n const parsed = DotenvModule._parseVault(options)\n\n let processEnv = process.env\n if (options && options.processEnv != null) {\n processEnv = options.processEnv\n }\n\n DotenvModule.populate(processEnv, parsed, options)\n\n return { parsed }\n}\n\nfunction configDotenv (options) {\n const dotenvPath = path.resolve(process.cwd(), '.env')\n let encoding = 'utf8'\n const debug = Boolean(options && options.debug)\n\n if (options && options.encoding) {\n encoding = options.encoding\n } else {\n if (debug) {\n _debug('No encoding is specified. UTF-8 is used by default')\n }\n }\n\n let optionPaths = [dotenvPath] // default, look for .env\n if (options && options.path) {\n if (!Array.isArray(options.path)) {\n optionPaths = [_resolveHome(options.path)]\n } else {\n optionPaths = [] // reset default\n for (const filepath of options.path) {\n optionPaths.push(_resolveHome(filepath))\n }\n }\n }\n\n // Build the parsed data in a temporary object (because we need to return it). Once we have the final\n // parsed data, we will combine it with process.env (or options.processEnv if provided).\n let lastError\n const parsedAll = {}\n for (const path of optionPaths) {\n try {\n // Specifying an encoding returns a string instead of a buffer\n const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }))\n\n DotenvModule.populate(parsedAll, parsed, options)\n } catch (e) {\n if (debug) {\n _debug(`Failed to load ${path} ${e.message}`)\n }\n lastError = e\n }\n }\n\n let processEnv = process.env\n if (options && options.processEnv != null) {\n processEnv = options.processEnv\n }\n\n DotenvModule.populate(processEnv, parsedAll, options)\n\n if (lastError) {\n return { parsed: parsedAll, error: lastError }\n } else {\n return { parsed: parsedAll }\n }\n}\n\n// Populates process.env from .env file\nfunction config (options) {\n // fallback to original dotenv if DOTENV_KEY is not set\n if (_dotenvKey(options).length === 0) {\n return DotenvModule.configDotenv(options)\n }\n\n const vaultPath = _vaultPath(options)\n\n // dotenvKey exists but .env.vault file does not exist\n if (!vaultPath) {\n _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`)\n\n return DotenvModule.configDotenv(options)\n }\n\n return DotenvModule._configVault(options)\n}\n\nfunction decrypt (encrypted, keyStr) {\n const key = Buffer.from(keyStr.slice(-64), 'hex')\n let ciphertext = Buffer.from(encrypted, 'base64')\n\n const nonce = ciphertext.subarray(0, 12)\n const authTag = ciphertext.subarray(-16)\n ciphertext = ciphertext.subarray(12, -16)\n\n try {\n const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce)\n aesgcm.setAuthTag(authTag)\n return `${aesgcm.update(ciphertext)}${aesgcm.final()}`\n } catch (error) {\n const isRange = error instanceof RangeError\n const invalidKeyLength = error.message === 'Invalid key length'\n const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data'\n\n if (isRange || invalidKeyLength) {\n const err = new Error('INVALID_DOTENV_KEY: It must be 64 characters long (or more)')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n } else if (decryptionFailed) {\n const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY')\n err.code = 'DECRYPTION_FAILED'\n throw err\n } else {\n throw error\n }\n }\n}\n\n// Populate process.env with parsed values\nfunction populate (processEnv, parsed, options = {}) {\n const debug = Boolean(options && options.debug)\n const override = Boolean(options && options.override)\n\n if (typeof parsed !== 'object') {\n const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate')\n err.code = 'OBJECT_REQUIRED'\n throw err\n }\n\n // Set process.env\n for (const key of Object.keys(parsed)) {\n if (Object.prototype.hasOwnProperty.call(processEnv, key)) {\n if (override === true) {\n processEnv[key] = parsed[key]\n }\n\n if (debug) {\n if (override === true) {\n _debug(`\"${key}\" is already defined and WAS overwritten`)\n } else {\n _debug(`\"${key}\" is already defined and was NOT overwritten`)\n }\n }\n } else {\n processEnv[key] = parsed[key]\n }\n }\n}\n\nconst DotenvModule = {\n configDotenv,\n _configVault,\n _parseVault,\n config,\n decrypt,\n parse,\n populate\n}\n\nmodule.exports.configDotenv = DotenvModule.configDotenv\nmodule.exports._configVault = DotenvModule._configVault\nmodule.exports._parseVault = DotenvModule._parseVault\nmodule.exports.config = DotenvModule.config\nmodule.exports.decrypt = DotenvModule.decrypt\nmodule.exports.parse = DotenvModule.parse\nmodule.exports.populate = DotenvModule.populate\n\nmodule.exports = DotenvModule\n","/**\n * DPML Agent模块\n *\n * 用于创建和管理AI对话代理。提供一组简洁的API,\n * 使应用开发者能够通过DPML定义AI助手,并与大语言模型服务进行交互。\n *\n * @example\n * ```typescript\n * import { createAgent } from '@dpml/agent';\n *\n * const agent = createAgent({\n * llm: {\n * apiType: 'openai',\n * apiKey: process.env.OPENAI_API_KEY,\n * model: 'gpt-4-turbo'\n * },\n * prompt: '你是一个专业的JavaScript和TypeScript助手。'\n * });\n *\n * const response = await agent.chat('如何在TypeScript中实现单例模式?');\n * console.log(response);\n * ```\n *\n * 或者使用DPML声明式语法:\n * ```typescript\n * import { createAgent, compiler } from '@dpml/agent';\n *\n * // DPML配置\n * const dpmlContent = `\n * <agent>\n * <llm api-type=\"openai\" api-key=\"@agentenv:OPENAI_API_KEY\" model=\"gpt-4-turbo\"></llm>\n * <prompt>你是一个专业的JavaScript和TypeScript助手。</prompt>\n * </agent>\n * `;\n *\n * // 编译DPML为AgentConfig\n * const config = await compiler.compile(dpmlContent);\n *\n * // 创建Agent\n * const agent = createAgent(config);\n * ```\n */\n\nimport { createDomainDPML } from '@dpml/core';\n\nimport { schema, transformers, commandsConfig } from './config';\nimport type { AgentConfig } from './types';\n\n// 导出API\nexport * from './api';\n\n// 导出类型定义\nexport * from './types';\n\n/**\n * 创建Agent领域DPML实例\n *\n * 集成Schema、转换器和CLI配置,\n * 提供编译和命令行功能。\n */\nexport const agentDPML = createDomainDPML<AgentConfig>({\n domain: 'agent',\n description: 'AI Agent Domain',\n schema,\n transformers,\n commands: commandsConfig,\n options: {\n strictMode: true,\n errorHandling: 'throw'\n }\n});\n\n/**\n * 导出编译器,便于直接使用\n */\nexport const compiler = agentDPML.compiler;\n","/**\n * 配置导出文件\n * 导出所有Agent的DPML配置组件\n */\n\n// 导出Schema\nexport { schema } from './schema';\n\n// 导出转换器\nexport { transformers, agentTransformer } from './transformers';\n\n// 导出CLI配置\nexport { commandsConfig } from './cli';\n","import type { DocumentSchema } from '@dpml/core';\n\n/**\n * Agent的DPML Schema定义\n *\n * 定义DPML文档的结构和约束规则,\n * 包括元素、属性和内容模型。\n */\nexport const schema: DocumentSchema = {\n // 根元素定义\n root: {\n element: 'agent',\n children: {\n elements: [\n { $ref: 'llm' },\n { $ref: 'prompt' },\n { $ref: 'experimental' }\n ]\n }\n },\n // 可复用类型定义\n types: [\n {\n // LLM配置元素\n element: 'llm',\n attributes: [\n {\n name: 'api-type',\n required: true\n },\n {\n name: 'api-url'\n },\n {\n name: 'api-key'\n },\n {\n name: 'model',\n required: true\n }\n ]\n },\n {\n // 提示词元素\n element: 'prompt',\n content: {\n type: 'text',\n required: true\n }\n },\n {\n // 实验性功能元素\n element: 'experimental',\n children: {\n elements: [\n { $ref: 'tools' }\n ]\n }\n },\n {\n // 工具集元素\n element: 'tools',\n children: {\n elements: [\n { $ref: 'tool' }\n ]\n }\n },\n {\n // 工具元素\n element: 'tool',\n attributes: [\n {\n name: 'name',\n required: true\n },\n {\n name: 'description',\n required: true\n }\n ]\n }\n ]\n};\n","import { createTransformerDefiner } from '@dpml/core';\nimport type { DPMLNode } from '@dpml/core';\n\nimport type { AgentConfig, LLMConfig } from '../types';\n\n// 创建转换器定义器\nconst definer = createTransformerDefiner();\n\n/**\n * Agent转换器\n *\n * 将DPML文档转换为AgentConfig对象\n */\nexport const agentTransformer = definer.defineStructuralMapper<unknown, AgentConfig>(\n 'agentTransformer',\n [\n {\n // 将LLM元素转换为LLM配置\n selector: \"agent > llm\",\n targetPath: \"llm\",\n transform: (value: unknown) => {\n const node = value as DPMLNode;\n const llmConfig: LLMConfig = {\n apiType: node.attributes.get('api-type') || '',\n apiUrl: node.attributes.get('api-url'),\n apiKey: node.attributes.get('api-key'),\n model: node.attributes.get('model') || ''\n };\n\n return llmConfig;\n }\n },\n {\n // 将prompt元素转换为提示词\n selector: \"agent > prompt\",\n targetPath: \"prompt\",\n transform: (value: unknown) => {\n const node = value as DPMLNode;\n\n return node.content || '';\n }\n }\n ]\n);\n\n// 导出所有转换器\nexport const transformers = [\n agentTransformer\n];\n","import fs from 'fs/promises';\nimport path from 'path';\nimport readline from 'readline';\n\nimport type { DomainCommandsConfig } from '@dpml/core';\nimport dotenv from 'dotenv';\n\nimport { createAgent } from '../api/agent';\nimport { replaceEnvVars } from '../api/agentenv';\n\n/**\n * 加载环境变量\n */\nfunction loadEnvironmentVariables(options: any): void {\n // 从env文件加载\n if (options.envFile) {\n const envPath = path.resolve(process.cwd(), options.envFile);\n\n dotenv.config({ path: envPath });\n }\n\n // 从命令行选项加载\n if (options.env) {\n for (const envVar of options.env) {\n const [key, value] = envVar.split('=');\n\n if (key && value) {\n process.env[key] = value;\n }\n }\n }\n}\n\n/**\n * 处理常规聊天(非流式)\n */\nexport async function handleRegularChat(agent: any, input: string): Promise<void> {\n try {\n // 发送消息并获取响应\n const response = await agent.chat(input);\n\n console.log('\\n' + response + '\\n');\n } catch (error) {\n console.error('错误:', error instanceof Error ? error.message : String(error));\n }\n}\n\n/**\n * 处理流式聊天\n */\nexport async function handleStreamChat(agent: any, input: string): Promise<void> {\n try {\n process.stdout.write('\\n'); // 输出开始新行\n\n for await (const chunk of agent.chatStream(input)) {\n process.stdout.write(chunk);\n }\n\n process.stdout.write('\\n\\n'); // 输出结束添加空行\n } catch (error) {\n console.error('\\n错误:', error instanceof Error ? error.message : String(error));\n }\n}\n\n/**\n * 执行交互式聊天\n */\nasync function executeChat(actionContext: any, filePath: string, options: any): Promise<void> {\n try {\n // 加载环境变量\n loadEnvironmentVariables(options);\n\n console.log('\\nDPML Agent Chat');\n console.log(`加载Agent配置: ${filePath}\\n`);\n\n // 读取文件内容\n const content = await fs.readFile(filePath, 'utf-8');\n\n // 使用编译器解析DPML\n const config = await actionContext.getCompiler().compile(content);\n\n // 处理环境变量\n const processedConfig = replaceEnvVars(config);\n\n // 创建Agent实例\n const agent = createAgent(processedConfig);\n\n // 创建交互界面\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout\n });\n\n console.log('你好,我是AI助手。有什么我可以帮你的?');\n\n // 确定是否使用流式输出(默认启用)\n const useStream = options.stream !== false;\n\n // 交互式聊天循环\n const askQuestion = () => {\n rl.question('> ', async (input) => {\n if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit' || input.toLowerCase() === 'bye') {\n console.log('会话结束。');\n rl.close();\n\n return;\n }\n\n // 根据选项选择处理方式\n if (useStream) {\n await handleStreamChat(agent, input);\n } else {\n await handleRegularChat(agent, input);\n }\n\n // 继续等待输入\n askQuestion();\n });\n };\n\n // 开始交互循环\n askQuestion();\n } catch (error) {\n console.error('错误:', error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n}\n\n/**\n * Agent CLI命令配置\n */\nexport const commandsConfig: DomainCommandsConfig = {\n // 包含标准validate命令\n includeStandard: true,\n\n // 自定义命令\n actions: [\n {\n name: 'chat',\n description: '启动与Agent的交互式聊天',\n args: [\n {\n name: 'filePath',\n description: 'Agent配置文件路径',\n required: true\n }\n ],\n options: [\n {\n flags: '-e, --env <KEY=VALUE...>',\n description: '设置环境变量'\n },\n {\n flags: '-f, --env-file <path>',\n description: '指定环境变量文件路径'\n },\n {\n flags: '-d, --debug',\n description: '启用调试模式'\n },\n {\n flags: '-s, --stream',\n description: '启用流式输出模式(默认开启)',\n defaultValue: true\n }\n ],\n action: executeChat\n }\n ]\n};\n","import { createAgent as createAgentCore } from '../core/agentService';\nimport type { Agent, AgentConfig } from '../types';\n\n/**\n * 创建Agent实例\n *\n * 基于提供的配置创建一个符合Agent接口的实例,用于与LLM交互。\n * 使用闭包模式封装内部状态,提供简洁的交互接口。\n *\n * @param config Agent配置信息\n * @returns 符合Agent接口的实例\n */\nexport function createAgent(config: AgentConfig): Agent {\n // 委托给agentService创建Agent实例\n return createAgentCore(config);\n}\n","import type { Agent, AgentConfig, ChatInput, ChatOutput, Content } from '../types';\nimport { AgentError, AgentErrorType } from '../types';\n\nimport { AgentRunner } from './AgentRunner';\nimport { createClient } from './llm/llmFactory';\nimport { InMemoryAgentSession } from './session/InMemoryAgentSession';\n\n/**\n * 创建符合Agent接口的实例\n *\n * @param config Agent配置\n * @returns Agent实例\n */\nexport function createAgent(config: AgentConfig): Agent {\n // 创建LLM客户端\n const llmClient = createClient(config.llm);\n\n // 创建会话管理器\n const session = new InMemoryAgentSession();\n\n // 创建AgentRunner实例\n const runner = new AgentRunner(config, llmClient, session);\n\n // 返回符合Agent接口的对象,使用闭包模式\n return {\n chat: (input: string | ChatInput) => handleChat(runner, input),\n chatStream: (input: string | ChatInput) => handleChatStream(runner, input)\n };\n}\n\n/**\n * 处理聊天请求\n */\nasync function handleChat(runner: AgentRunner, input: string | ChatInput): Promise<string> {\n try {\n // 标准化输入为ChatInput\n const chatInput = normalizeChatInput(input);\n\n // 发送消息并获取响应\n const response = await runner.sendMessage(chatInput, false);\n\n // 确保响应不是异步迭代器\n if (Symbol.asyncIterator in response) {\n throw new AgentError(\n '意外收到流式响应',\n AgentErrorType.UNKNOWN,\n 'UNEXPECTED_STREAM_RESPONSE'\n );\n }\n\n // 提取回复中的文本内容\n return extractTextFromContent(response.content);\n } catch (error: unknown) {\n // 已经是AgentError则直接抛出\n if (error instanceof AgentError) {\n throw error;\n }\n\n // 否则包装为AgentError\n throw new AgentError(\n `聊天请求处理失败: ${error instanceof Error ? error.message : String(error)}`,\n AgentErrorType.UNKNOWN,\n 'CHAT_PROCESSING_ERROR',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n}\n\n/**\n * 处理流式聊天请求\n */\nasync function* handleChatStream(runner: AgentRunner, input: string | ChatInput): AsyncIterable<string> {\n // 标准化输入为ChatInput\n const chatInput = normalizeChatInput(input);\n\n try {\n // 发送消息并获取流式响应\n const responseStream = await runner.sendMessage(chatInput, true);\n\n // 确保响应是异步迭代器\n if (!(Symbol.asyncIterator in responseStream)) {\n // 如果收到单一响应而非流,也将其作为单个块返回\n yield extractTextFromContent((responseStream as ChatOutput).content);\n\n return;\n }\n\n // 处理流式响应\n for await (const chunk of responseStream as AsyncIterable<ChatOutput>) {\n // 提取每个块中的文本内容\n const textContent = extractTextFromContent(chunk.content);\n\n if (textContent) {\n yield textContent;\n }\n }\n } catch (error: unknown) {\n // 已经是AgentError则直接抛出\n if (error instanceof AgentError) {\n throw error;\n }\n\n // 否则包装为AgentError\n throw new AgentError(\n `流式聊天请求处理失败: ${error instanceof Error ? error.message : String(error)}`,\n AgentErrorType.UNKNOWN,\n 'STREAM_PROCESSING_ERROR',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n}\n\n/**\n * 将字符串或ChatInput标准化为ChatInput\n */\nfunction normalizeChatInput(input: string | ChatInput): ChatInput {\n if (typeof input === 'string') {\n return {\n content: {\n type: 'text',\n value: input\n }\n };\n }\n\n return input;\n}\n\n/**\n * 从内容中提取文本\n */\nfunction extractTextFromContent(content: Content): string {\n if (Array.isArray(content)) {\n // 寻找数组中的第一个文本内容\n const textItem = content.find(item => item.type === 'text');\n\n return textItem ? textItem.value as string : '';\n } else if (content.type === 'text') {\n // 直接返回文本内容\n return content.value as string;\n }\n\n return '';\n}\n","/**\n * Agent模块类型定义\n *\n * 统一导出所有公共类型\n */\nexport type { Agent } from './Agent';\nexport type { AgentConfig } from './AgentConfig';\nexport type { LLMConfig } from './LLMConfig';\nexport type { Content, ContentItem, ContentType } from './Content';\nexport type { ChatInput, ChatOutput } from './Chat';\nexport { AgentError, AgentErrorType } from './errors';\n","/**\n * Agent错误类型\n */\nexport enum AgentErrorType {\n /**\n * 配置错误\n */\n CONFIG = 'CONFIG',\n\n /**\n * LLM服务调用错误\n */\n LLM_SERVICE = 'LLM_SERVICE',\n\n /**\n * 内容处理错误\n */\n CONTENT = 'CONTENT',\n\n /**\n * 会话错误\n */\n SESSION = 'SESSION',\n\n /**\n * 未知错误\n */\n UNKNOWN = 'UNKNOWN'\n}\n\n/**\n * Agent错误\n *\n * 统一的错误类,用于处理Agent模块中的所有错误。\n */\nexport class AgentError extends Error {\n /**\n * 错误类型\n */\n readonly type: AgentErrorType;\n\n /**\n * 错误码\n */\n readonly code: string;\n\n /**\n * 原始错误\n */\n readonly cause?: Error;\n\n /**\n * 创建Agent错误\n *\n * @param message 错误消息\n * @param type 错误类型\n * @param code 错误码\n * @param cause 原始错误\n */\n constructor(\n message: string,\n type: AgentErrorType = AgentErrorType.UNKNOWN,\n code: string = 'AGENT_ERROR',\n cause?: Error\n ) {\n super(message);\n this.name = 'AgentError';\n this.type = type;\n this.code = code;\n this.cause = cause;\n }\n}\n","import type { AgentConfig, ChatInput, ChatOutput } from '../types';\n\nimport type { LLMClient } from './llm/LLMClient';\nimport type { AgentSession } from './session/AgentSession';\nimport type { Message } from './types';\n\n/**\n * Agent运行器\n *\n * 负责处理消息发送和接收的核心类。\n */\nexport class AgentRunner {\n private config: AgentConfig;\n private llmClient: LLMClient;\n private session: AgentSession;\n\n constructor(config: AgentConfig, llmClient: LLMClient, session: AgentSession) {\n this.config = config;\n this.llmClient = llmClient;\n this.session = session;\n }\n\n /**\n * 发送消息并获取响应\n *\n * @param input 输入内容\n * @param stream 是否使用流式响应\n * @returns 响应内容或流式响应迭代器\n */\n public sendMessage(input: ChatInput, stream: boolean): Promise<ChatOutput | AsyncIterable<ChatOutput>> {\n // 创建用户消息\n const userMessage: Message = {\n role: 'user',\n content: input.content\n };\n\n // 添加到会话历史\n this.session.addMessage(userMessage);\n\n // 准备发送给LLM的消息列表\n const messages = this.prepareMessages();\n\n // 发送消息给LLM客户端\n return this.llmClient.sendMessages(messages, stream);\n }\n\n /**\n * 准备发送给LLM的消息列表\n *\n * @returns 按照正确顺序排列的消息列表\n */\n private prepareMessages(): Message[] {\n const messages: Message[] = [];\n\n // 添加系统提示(总是在第一位)\n if (this.config.prompt) {\n messages.push({\n role: 'system',\n content: {\n type: 'text',\n value: this.config.prompt\n }\n });\n }\n\n // 添加所有历史消息\n // 在测试中历史消息包括当前用户消息,所以不需要专门分离处理\n const historyMessages = this.session.getMessages();\n\n if (historyMessages.length > 0) {\n messages.push(...historyMessages);\n }\n\n return messages;\n }\n}\n","import { AgentError, AgentErrorType } from '../../types';\nimport type { LLMConfig } from '../../types';\n\nimport { AnthropicClient } from './AnthropicClient';\nimport type { LLMClient } from './LLMClient';\nimport { OpenAIClient } from './OpenAIClient';\n\n/**\n * 验证LLM配置的基本有效性\n * @param config LLM配置\n */\nfunction validateConfig(config: LLMConfig): void {\n // 验证模型名称\n if (!config.model) {\n throw new AgentError(\n '缺少必要的模型名称参数',\n AgentErrorType.CONFIG,\n 'MISSING_MODEL_NAME'\n );\n }\n\n // 验证OpenAI配置\n if (config.apiType.toLowerCase() === 'openai' && !config.apiKey) {\n throw new AgentError(\n 'OpenAI API密钥未提供',\n AgentErrorType.CONFIG,\n 'MISSING_API_KEY'\n );\n }\n\n // 验证Anthropic配置\n if (config.apiType.toLowerCase() === 'anthropic' && !config.apiKey) {\n throw new AgentError(\n 'Anthropic API密钥未提供',\n AgentErrorType.CONFIG,\n 'MISSING_API_KEY'\n );\n }\n}\n\n/**\n * 创建LLM客户端实例\n *\n * @param config LLM配置\n * @returns LLM客户端实例\n */\nexport function createClient(config: LLMConfig): LLMClient {\n // 首先验证配置的基本有效性\n validateConfig(config);\n\n switch (config.apiType.toLowerCase()) {\n case 'openai':\n return new OpenAIClient(config);\n case 'anthropic':\n return new AnthropicClient(config);\n default:\n throw new AgentError(\n `不支持的API类型: ${config.apiType}`,\n AgentErrorType.CONFIG,\n 'UNSUPPORTED_LLM_TYPE'\n );\n }\n}\n","import type { ChatOutput } from '../../types/Chat';\nimport type { ContentItem } from '../../types/Content';\nimport { AgentError, AgentErrorType } from '../../types/errors';\nimport type { LLMConfig } from '../../types/LLMConfig';\nimport type { Message } from '../types';\n\nimport type { LLMClient } from './LLMClient';\n\n/**\n * Anthropic客户端实现\n */\nexport class AnthropicClient implements LLMClient {\n private apiKey: string;\n private apiUrl: string;\n private model: string;\n\n constructor(config: LLMConfig) {\n this.apiKey = config.apiKey || '';\n this.apiUrl = config.apiUrl || 'https://api.anthropic.com/v1';\n this.model = config.model;\n\n if (!this.apiKey) {\n throw new AgentError(\n 'Anthropic API密钥未提供',\n AgentErrorType.CONFIG,\n 'MISSING_API_KEY'\n );\n }\n }\n\n public async sendMessages(messages: Message[], stream: boolean): Promise<ChatOutput | AsyncIterable<ChatOutput>> {\n try {\n // 转换为Anthropic消息格式\n const anthropicMessages = this.convertToAnthropicMessages(messages);\n\n // 选择同步或流式API\n if (stream) {\n return this.streamMessages(anthropicMessages);\n } else {\n return this.sendMessagesSync(anthropicMessages);\n }\n } catch (error: unknown) {\n // 将底层错误包装为AgentError\n throw new AgentError(\n `LLM服务调用失败: ${error instanceof Error ? error.message : String(error)}`,\n AgentErrorType.LLM_SERVICE,\n 'LLM_API_ERROR',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n private async sendMessagesSync(anthropicMessages: Record<string, unknown>): Promise<ChatOutput> {\n // 准备请求参数\n const requestBody = {\n model: this.model,\n messages: anthropicMessages.messages,\n max_tokens: 1024,\n stream: false\n };\n\n // 发送请求到Anthropic API\n const response = await fetch(`${this.apiUrl}/messages`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': this.apiKey,\n 'anthropic-version': '2023-06-01'\n },\n body: JSON.stringify(requestBody)\n });\n\n // 检查响应状态\n if (!response.ok) {\n const errorData = await response.text();\n\n throw new Error(`Anthropic API请求失败: ${response.status} ${response.statusText} - ${errorData}`);\n }\n\n // 解析响应\n const data = await response.json();\n const content = data.content?.[0]?.text;\n\n if (!content) {\n throw new Error('Anthropic响应格式无效,没有找到内容');\n }\n\n // 返回标准化的输出\n return {\n content: {\n type: 'text',\n value: content\n }\n };\n }\n\n private async *streamMessages(anthropicMessages: Record<string, unknown>): AsyncIterable<ChatOutput> {\n // 准备请求参数\n const requestBody = {\n model: this.model,\n messages: anthropicMessages.messages,\n max_tokens: 1024,\n stream: true\n };\n\n // 发送请求到Anthropic API\n const response = await fetch(`${this.apiUrl}/messages`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': this.apiKey,\n 'anthropic-version': '2023-06-01'\n },\n body: JSON.stringify(requestBody)\n });\n\n // 检查响应状态\n if (!response.ok) {\n const errorData = await response.text();\n\n throw new Error(`Anthropic API流式请求失败: ${response.status} ${response.statusText} - ${errorData}`);\n }\n\n if (!response.body) {\n throw new Error('Anthropic API流式响应没有响应体');\n }\n\n // 创建流式读取器\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n // 解码新接收的数据\n buffer += decoder.decode(value, { stream: true });\n\n // 处理接收到的事件\n const lines = buffer.split('\\n');\n\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.startsWith('data: ') && line !== 'data: [DONE]') {\n const jsonData = line.substring(6); // 去掉 'data: ' 前缀\n\n try {\n const data = JSON.parse(jsonData);\n\n if (data.type === 'content_block_delta' && data.delta?.text) {\n yield {\n content: {\n type: 'text',\n value: data.delta.text\n }\n };\n }\n } catch (e) {\n console.error('解析SSE事件失败:', e);\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n private convertToAnthropicMessages(messages: Message[]): Record<string, unknown> {\n // Anthropic格式要求系统提示和用户消息分开处理\n const systemMessage = messages.find(msg => msg.role === 'system');\n\n // 过滤掉系统消息,只保留用户和助手消息\n const userAssistantMessages = messages\n .filter(msg => msg.role !== 'system')\n .map(msg => ({\n role: msg.role,\n content: this.convertContent(msg.content)\n }));\n\n // 构建Anthropic请求格式\n return {\n system: systemMessage ? this.extractTextContent(systemMessage.content) : '',\n messages: userAssistantMessages\n };\n }\n\n private convertContent(content: ContentItem | ContentItem[]): unknown {\n // Anthropic消息内容处理\n if (Array.isArray(content)) {\n // 暂时只支持文本内容,将数组中的文本合并\n return content\n .filter(item => item.type === 'text')\n .map(item => item.value)\n .join('\\n');\n } else if (content.type === 'text') {\n return content.value;\n }\n\n // 不支持的内容类型\n return `[不支持的内容类型: ${Array.isArray(content) ? '多模态内容' : content.type}]`;\n }\n\n private extractTextContent(content: ContentItem | ContentItem[]): string {\n if (Array.isArray(content)) {\n return content\n .filter(item => item.type === 'text')\n .map(item => item.value as string)\n .join('\\n');\n } else if (content.type === 'text') {\n return content.value as string;\n }\n\n return '';\n }\n}\n","import type { ChatOutput } from '../../types/Chat';\nimport type { ContentItem } from '../../types/Content';\nimport { AgentError, AgentErrorType } from '../../types/errors';\nimport type { LLMConfig } from '../../types/LLMConfig';\nimport type { Message } from '../types';\n\nimport type { LLMClient } from './LLMClient';\n\n/**\n * OpenAI客户端实现\n */\nexport class OpenAIClient implements LLMClient {\n private apiKey: string;\n private apiUrl: string;\n private model: string;\n\n constructor(config: LLMConfig) {\n this.apiKey = config.apiKey || '';\n this.apiUrl = config.apiUrl || 'https://api.openai.com/v1';\n this.model = config.model;\n\n if (!this.apiKey) {\n throw new AgentError(\n 'OpenAI API密钥未提供',\n AgentErrorType.CONFIG,\n 'MISSING_API_KEY'\n );\n }\n }\n\n public async sendMessages(messages: Message[], stream: boolean): Promise<ChatOutput | AsyncIterable<ChatOutput>> {\n try {\n // 转换为OpenAI格式的消息\n const openaiMessages = this.convertToOpenAIMessages(messages);\n\n // 选择同步或流式API\n if (stream) {\n return this.streamMessages(openaiMessages);\n } else {\n return this.sendMessagesSync(openaiMessages);\n }\n } catch (error: unknown) {\n // 将底层错误包装为AgentError\n // 如果已经是AgentError类型,则直接抛出\n if (error instanceof AgentError) {\n throw error;\n }\n\n throw new AgentError(\n `LLM服务调用失败: ${error instanceof Error ? error.message : String(error)}`,\n AgentErrorType.LLM_SERVICE,\n 'LLM_API_ERROR',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n private async sendMessagesSync(openaiMessages: Record<string, unknown>[]): Promise<ChatOutput> {\n try {\n // 准备请求参数\n const requestBody = {\n model: this.model,\n messages: openaiMessages,\n stream: false\n };\n\n // 发送请求到OpenAI API\n const response = await fetch(`${this.apiUrl}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify(requestBody)\n });\n\n // 检查响应状态\n if (!response.ok) {\n const errorData = await response.text();\n\n throw new AgentError(\n `OpenAI API请求失败: ${response.status} ${response.statusText} - ${errorData}`,\n AgentErrorType.LLM_SERVICE,\n 'LLM_API_ERROR'\n );\n }\n\n // 解析响应\n const data = await response.json();\n const content = data.choices[0]?.message?.content;\n\n if (!content) {\n throw new AgentError(\n 'OpenAI响应格式无效,没有找到内容',\n AgentErrorType.LLM_SERVICE,\n 'INVALID_RESPONSE_FORMAT'\n );\n }\n\n // 返回标准化的输出\n return {\n content: {\n type: 'text',\n value: content\n }\n };\n } catch (error) {\n // 确保所有错误都被包装成AgentError\n if (error instanceof AgentError) {\n throw error;\n }\n\n throw new AgentError(\n `LLM服务调用失败: ${error instanceof Error ? error.message : String(error)}`,\n AgentErrorType.LLM_SERVICE,\n 'LLM_API_ERROR',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n private async *streamMessages(openaiMessages: Record<string, unknown>[]): AsyncIterable<ChatOutput> {\n try {\n // 准备请求参数\n const requestBody = {\n model: this.model,\n messages: openaiMessages,\n stream: true\n };\n\n // 发送请求到OpenAI API\n const response = await fetch(`${this.apiUrl}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`\n },\n body: JSON.stringify(requestBody)\n });\n\n // 检查响应状态\n if (!response.ok) {\n const errorData = await response.text();\n\n throw new AgentError(\n `OpenAI API流式请求失败: ${response.status} ${response.statusText} - ${errorData}`,\n AgentErrorType.LLM_SERVICE,\n 'LLM_API_ERROR'\n );\n }\n\n if (!response.body) {\n throw new AgentError(\n 'OpenAI API流式响应没有响应体',\n AgentErrorType.LLM_SERVICE,\n 'MISSING_RESPONSE_BODY'\n );\n }\n\n // 创建流式读取器\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n // 解码新接收的数据\n buffer += decoder.decode(value, { stream: true });\n\n // 处理接收到的事件\n const lines = buffer.split('\\n');\n\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.startsWith('data: ') && line !== 'data: [DONE]') {\n const jsonData = line.substring(6); // 去掉 'data: ' 前缀\n\n try {\n const data = JSON.parse(jsonData);\n const content = data.choices[0]?.delta?.content;\n\n if (content) {\n yield {\n content: {\n type: 'text',\n value: content\n }\n };\n }\n } catch (e) {\n console.error('解析SSE事件失败:', e);\n throw new AgentError(\n `解析SSE事件失败: ${e instanceof Error ? e.message : String(e)}`,\n AgentErrorType.LLM_SERVICE,\n 'SSE_PARSING_ERROR',\n e instanceof Error ? e : new Error(String(e))\n );\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n } catch (error) {\n // 确保所有错误都被包装成AgentError\n if (error instanceof AgentError) {\n throw error;\n }\n\n throw new AgentError(\n `LLM服务调用失败: ${error instanceof Error ? error.message : String(error)}`,\n AgentErrorType.LLM_SERVICE,\n 'LLM_API_ERROR',\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n private convertToOpenAIMessages(messages: Message[]): Record<string, unknown>[] {\n // 转换消息格式为OpenAI API格式\n return messages.map(msg => {\n return {\n role: msg.role,\n content: this.convertContent(msg.content)\n };\n });\n }\n\n private convertContent(content: ContentItem | ContentItem[]): unknown {\n // 处理多模态内容转换为OpenAI格式\n if (Array.isArray(content)) {\n // OpenAI格式支持内容数组\n return content.map(item => this.convertContentItem(item));\n }\n\n return this.convertContentItem(content);\n }\n\n private convertContentItem(item: ContentItem): unknown {\n switch (item.type) {\n case 'text':\n return item.value as string;\n case 'image':\n // 处理图像类型,转换为OpenAI的图像URL格式\n return {\n type: 'image_url',\n image_url: {\n url: `data:${item.mimeType || 'image/jpeg'};base64,${this.arrayBufferToBase64(item.value as Uint8Array)}`\n }\n };\n case 'audio':\n case 'video':\n case 'file':\n default:\n // 暂不支持其他内容类型,转换为文本提示\n return `[不支持的内容类型: ${item.type}]`;\n }\n }\n\n private arrayBufferToBase64(buffer: Uint8Array): string {\n // 将Uint8Array转换为Base64编码的字符串\n if (typeof Buffer !== 'undefined') {\n // Node.js环境\n return Buffer.from(buffer).toString('base64');\n } else {\n // 浏览器环境\n let binary = '';\n const len = buffer.byteLength;\n\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n\n // 使用btoa函数(浏览器内置)进行Base64编码\n return btoa(binary);\n }\n }\n}\n","import type { Message } from '../types';\n\nimport type { AgentSession } from './AgentSession';\n\n/**\n * 内存会话实现\n */\nexport class InMemoryAgentSession implements AgentSession {\n private messages: Message[] = [];\n private capacity: number;\n\n constructor(capacity: number = 100) {\n this.capacity = capacity;\n }\n\n public addMessage(message: Message): void {\n this.messages.push(message);\n\n // 如果超出容量,删除最早的消息\n if (this.messages.length > this.capacity) {\n this.messages.shift();\n }\n }\n\n public getMessages(): ReadonlyArray<Message> {\n return [...this.messages];\n }\n}\n","import { replaceEnvVars as replaceEnvVarsCore } from '../core/agentenv/agentenvCore';\n\n/**\n * 替换值中的环境变量引用\n *\n * 识别并替换字符串、数组或对象中的所有 @agentenv:ENV_NAME 格式的环境变量引用。\n * 支持任意嵌套的数据结构。\n *\n * @param value 包含环境变量引用的值(字符串、数组或对象)\n * @returns 替换后的值,保持原始类型\n * @example\n * // 替换字符串\n * const apiKey = replaceEnvVars('@agentenv:API_KEY');\n *\n * // 替换对象中的值\n * const config = replaceEnvVars({\n * apiKey: '@agentenv:API_KEY',\n * endpoint: '@agentenv:API_ENDPOINT'\n * });\n */\nexport function replaceEnvVars<T>(value: T): T {\n return replaceEnvVarsCore(value);\n}\n","import { ENV_VAR_PATTERN } from './constants';\n\n/**\n * 替换值中的环境变量引用(Core实现)\n */\nexport function replaceEnvVars<T>(value: T): T {\n // 处理null/undefined\n if (value === null || value === undefined) {\n return value;\n }\n\n // 处理字符串\n if (typeof value === 'string') {\n return replaceInString(value) as unknown as T;\n }\n\n // 处理数组\n if (Array.isArray(value)) {\n return value.map(item => replaceEnvVars(item)) as unknown as T;\n }\n\n // 处理对象\n if (typeof value === 'object') {\n const result: Record<string, any> = {};\n\n for (const key in value as Record<string, any>) {\n result[key] = replaceEnvVars((value as Record<string, any>)[key]);\n }\n\n return result as T;\n }\n\n // 其他类型直接返回\n return value;\n}\n\n/**\n * 替换字符串中的环境变量引用\n */\nfunction replaceInString(value: string): string {\n return value.replace(ENV_VAR_PATTERN, (_match, envName) => {\n const envValue = process.env[envName];\n\n if (envValue === undefined) {\n console.warn(`警告: 环境变量 ${envName} 未定义`);\n\n return _match; // 保留原始表达式\n }\n\n return envValue;\n });\n}\n","/**\n * 环境变量引用的正则表达式模式\n * 匹配 @agentenv:ENV_NAME 格式\n */\nexport const ENV_VAR_PATTERN = /@agentenv:([A-Z0-9_]+)/g;\n\n/**\n * 环境变量引用前缀\n */\nexport const ENV_VAR_PREFIX = '@agentenv:';\n","/**\n * Agent模块的API层入口\n *\n * 导出所有公共API\n */\nexport * from './agent';\n","#!/usr/bin/env node\n/**\n * DPML Agent CLI工具\n *\n * 提供命令行接口,用于管理和使用通过DPML定义的Agent。\n * 基于@dpml/core的CLI基础设施实现。\n *\n * 使用示例:\n * ```bash\n * # 验证Agent配置\n * dpml agent validate agent-config.xml\n *\n * # 与Agent交互\n * dpml agent chat agent-config.xml\n *\n * # 使用环境变量\n * dpml agent chat agent-config.xml --env API_KEY=sk-xxxx\n * ```\n */\n\nimport { agentDPML } from './index';\n\n/**\n * CLI主函数\n */\nasync function main() {\n try {\n // 执行CLI命令\n await agentDPML.cli.execute();\n } catch (error) {\n console.error('CLI执行出错:', error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n}\n\n// 运行主函数\nmain();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;ACAA;AAAA;AAAA;AAAA,MACE,MAAQ;AAAA,MACR,SAAW;AAAA,MACX,aAAe;AAAA,MACf,MAAQ;AAAA,MACR,OAAS;AAAA,MACT,SAAW;AAAA,QACT,KAAK;AAAA,UACH,OAAS;AAAA,UACT,SAAW;AAAA,UACX,SAAW;AAAA,QACb;AAAA,QACA,YAAY;AAAA,QACZ,eAAe;AAAA,QACf,qBAAqB;AAAA,QACrB,wBAAwB;AAAA,QACxB,qBAAqB;AAAA,QACrB,wBAAwB;AAAA,QACxB,kBAAkB;AAAA,MACpB;AAAA,MACA,SAAW;AAAA,QACT,aAAa;AAAA,QACb,MAAQ;AAAA,QACR,SAAW;AAAA,QACX,MAAQ;AAAA,QACR,iBAAiB;AAAA,QACjB,YAAc;AAAA,QACd,SAAW;AAAA,MACb;AAAA,MACA,YAAc;AAAA,QACZ,MAAQ;AAAA,QACR,KAAO;AAAA,MACT;AAAA,MACA,UAAY;AAAA,MACZ,SAAW;AAAA,MACX,UAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,gBAAkB;AAAA,MAClB,SAAW;AAAA,MACX,iBAAmB;AAAA,QACjB,eAAe;AAAA,QACf,SAAW;AAAA,QACX,OAAS;AAAA,QACT,UAAY;AAAA,QACZ,oBAAoB;AAAA,QACpB,KAAO;AAAA,QACP,YAAc;AAAA,MAChB;AAAA,MACA,SAAW;AAAA,QACT,MAAQ;AAAA,MACV;AAAA,MACA,SAAW;AAAA,QACT,IAAM;AAAA,MACR;AAAA,IACF;AAAA;AAAA;;;AC7DA;;;;QAAMA,MAAKC,UAAQ,IAAA;AACnB,QAAMC,QAAOD,UAAQ,MAAA;AACrB,QAAME,KAAKF,UAAQ,IAAA;AACnB,QAAMG,SAASH,UAAQ,QAAA;AACvB,QAAMI,cAAcJ;AAEpB,QAAMK,UAAUD,YAAYC;AAE5B,QAAMC,OAAO;AAGb,aAASC,MAAOC,KAAG;AACjB,YAAMC,MAAM,CAAC;AAGb,UAAIC,QAAQF,IAAIG,SAAQ;AAGxBD,cAAQA,MAAME,QAAQ,WAAW,IAAA;AAEjC,UAAIC;AACJ,cAAQA,QAAQP,KAAKQ,KAAKJ,KAAAA,MAAW,MAAM;AACzC,cAAMK,MAAMF,MAAM,CAAA;AAGlB,YAAIG,QAASH,MAAM,CAAA,KAAM;AAGzBG,gBAAQA,MAAMC,KAAI;AAGlB,cAAMC,aAAaF,MAAM,CAAA;AAGzBA,gBAAQA,MAAMJ,QAAQ,0BAA0B,IAAA;AAGhD,YAAIM,eAAe,KAAK;AACtBF,kBAAQA,MAAMJ,QAAQ,QAAQ,IAAA;AAC9BI,kBAAQA,MAAMJ,QAAQ,QAAQ,IAAA;QAChC;AAGAH,YAAIM,GAAAA,IAAOC;MACb;AAEA,aAAOP;IACT;AApCSF;AAsCT,aAASY,YAAaC,SAAO;AAC3B,YAAMC,YAAYC,WAAWF,OAAAA;AAG7B,YAAMG,SAASC,aAAaC,aAAa;QAAExB,MAAMoB;MAAU,CAAA;AAC3D,UAAI,CAACE,OAAOG,QAAQ;AAClB,cAAMC,MAAM,IAAIC,MAAM,8BAA8BP,SAAAA,wBAAiC;AACrFM,YAAIE,OAAO;AACX,cAAMF;MACR;AAIA,YAAMG,OAAOC,WAAWX,OAAAA,EAASY,MAAM,GAAA;AACvC,YAAMC,SAASH,KAAKG;AAEpB,UAAIC;AACJ,eAASC,IAAI,GAAGA,IAAIF,QAAQE,KAAK;AAC/B,YAAI;AAEF,gBAAMpB,MAAMe,KAAKK,CAAAA,EAAGlB,KAAI;AAGxB,gBAAMmB,QAAQC,cAAcd,QAAQR,GAAAA;AAGpCmB,sBAAYV,aAAac,QAAQF,MAAMG,YAAYH,MAAMrB,GAAG;AAE5D;QACF,SAASyB,OAAO;AAEd,cAAIL,IAAI,KAAKF,QAAQ;AACnB,kBAAMO;UACR;QAEF;MACF;AAGA,aAAOhB,aAAajB,MAAM2B,SAAAA;IAC5B;AAxCSf;AA0CT,aAASsB,MAAOC,SAAO;AACrBC,cAAQC,IAAI,WAAWvC,OAAAA