oricore 1.3.0 → 1.3.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.
package/README.md CHANGED
@@ -74,11 +74,11 @@ const engine = createEngine({
74
74
 
75
75
  // 2. Initialize with model and API key
76
76
  await engine.initialize({
77
- model: 'zhipuai/glm-4.7',
77
+ model: 'openai/gpt-5.2-codex',
78
78
  provider: {
79
- zhipuai: {
79
+ openai: {
80
80
  apiKey: 'your-api-key',
81
- baseURL: 'https://open.bigmodel.cn/api/paas/v4',
81
+ baseURL: 'https://api.openai.com/v1',
82
82
  },
83
83
  },
84
84
  });
@@ -142,19 +142,19 @@ OriCore includes a comprehensive set of tools:
142
142
 
143
143
  ```typescript
144
144
  await engine.initialize({
145
- model: 'zhipuai/glm-4.7',
146
- planModel: 'zhipuai/glm-4.7',
145
+ model: 'openai/gpt-5.2-codex',
146
+ planModel: 'openai/gpt-5.2-codex',
147
147
  approvalMode: 'autoEdit',
148
- language: 'zh-CN',
148
+ language: 'en',
149
149
  tools: {
150
150
  read: true,
151
151
  write: true,
152
152
  bash: true,
153
153
  },
154
154
  provider: {
155
- zhipuai: {
155
+ openai: {
156
156
  apiKey: 'your-api-key',
157
- baseURL: 'https://open.bigmodel.cn/api/paas/v4',
157
+ baseURL: 'https://api.openai.com/v1',
158
158
  },
159
159
  },
160
160
  });
@@ -164,11 +164,11 @@ await engine.initialize({
164
164
 
165
165
  | Provider | Model Example | API Base URL |
166
166
  |----------|---------------|--------------|
167
- | Zhipu AI | `zhipuai/glm-4.7` | `https://open.bigmodel.cn/api/paas/v4` |
167
+ | OpenAI | `openai/gpt-5.2-codex` | `https://api.openai.com/v1` |
168
+ | Anthropic | `anthropic/claude-opus-4-5` | `https://api.anthropic.com` |
169
+ | Google | `google/gemini-3-flash-preview` | `https://generativelanguage.googleapis.com` |
168
170
  | DeepSeek | `deepseek/deepseek-chat` | `https://api.deepseek.com` |
169
- | OpenAI | `openai/gpt-4o` | `https://api.openai.com/v1` |
170
- | Anthropic | `anthropic/claude-sonnet-4` | `https://api.anthropic.com` |
171
- | Google | `google/gemini-2.5-flash` | `https://generativelanguage.googleapis.com` |
171
+ | Zhipu AI | `zhipuai/glm-4.7` | `https://open.bigmodel.cn/api/paas/v4` |
172
172
 
173
173
  See [USAGE.md](./USAGE.md) for more configuration options.
174
174
 
@@ -190,6 +190,13 @@ oricore/
190
190
  └── dist/ # Compiled output
191
191
  ```
192
192
 
193
+ ## Statement
194
+
195
+ This project references the core architecture of the following excellent project:
196
+ - **[neovate-code](https://github.com/neovateai/neovate-code)** - Core AI engine architecture
197
+
198
+ OriCore has been refactored and streamlined on this foundation, removing UI, CLI, and other peripheral features to focus on providing a lightweight, standalone AI engine library that can be easily integrated into any project.
199
+
193
200
  ## License
194
201
 
195
202
  MIT © [lyw405](https://github.com/lyw405)
package/README.zh-CN.md CHANGED
@@ -74,11 +74,11 @@ const engine = createEngine({
74
74
 
75
75
  // 2. 初始化模型和 API Key
76
76
  await engine.initialize({
77
- model: 'zhipuai/glm-4.7',
77
+ model: 'openai/gpt-5.2-codex',
78
78
  provider: {
79
- zhipuai: {
79
+ openai: {
80
80
  apiKey: 'your-api-key',
81
- baseURL: 'https://open.bigmodel.cn/api/paas/v4',
81
+ baseURL: 'https://api.openai.com/v1',
82
82
  },
83
83
  },
84
84
  });
@@ -142,8 +142,8 @@ OriCore 包含一套完整的工具:
142
142
 
143
143
  ```typescript
144
144
  await engine.initialize({
145
- model: 'zhipuai/glm-4.7',
146
- planModel: 'zhipuai/glm-4.7',
145
+ model: 'openai/gpt-5.2-codex',
146
+ planModel: 'openai/gpt-5.2-codex',
147
147
  approvalMode: 'autoEdit',
148
148
  language: 'zh-CN',
149
149
  tools: {
@@ -152,9 +152,9 @@ await engine.initialize({
152
152
  bash: true,
153
153
  },
154
154
  provider: {
155
- zhipuai: {
155
+ openai: {
156
156
  apiKey: 'your-api-key',
157
- baseURL: 'https://open.bigmodel.cn/api/paas/v4',
157
+ baseURL: 'https://api.openai.com/v1',
158
158
  },
159
159
  },
160
160
  });
@@ -164,11 +164,11 @@ await engine.initialize({
164
164
 
165
165
  | 提供商 | 模型示例 | API 地址 |
166
166
  |----------|---------------|--------------|
167
- | 智谱 AI | `zhipuai/glm-4.7` | `https://open.bigmodel.cn/api/paas/v4` |
167
+ | OpenAI | `openai/gpt-5.2-codex` | `https://api.openai.com/v1` |
168
+ | Anthropic | `anthropic/claude-opus-4-5` | `https://api.anthropic.com` |
169
+ | Google | `google/gemini-3-flash-preview` | `https://generativelanguage.googleapis.com` |
168
170
  | DeepSeek | `deepseek/deepseek-chat` | `https://api.deepseek.com` |
169
- | OpenAI | `openai/gpt-4o` | `https://api.openai.com/v1` |
170
- | Anthropic | `anthropic/claude-sonnet-4` | `https://api.anthropic.com` |
171
- | Google | `google/gemini-2.5-flash` | `https://generativelanguage.googleapis.com` |
171
+ | 智谱 AI | `zhipuai/glm-4.7` | `https://open.bigmodel.cn/api/paas/v4` |
172
172
 
173
173
  更多配置选项请参阅 [USAGE.zh-CN.md](./USAGE.zh-CN.md)。
174
174
 
@@ -190,6 +190,13 @@ oricore/
190
190
  └── dist/ # 编译输出
191
191
  ```
192
192
 
193
+ ## 声明
194
+
195
+ 本项目参考了以下优秀项目的核心架构:
196
+ - **[neovate-code](https://github.com/neovateai/neovate-code)** - 核心 AI 引擎架构
197
+
198
+ OriCore 在此基础上进行了重新封装和精简,移除了 UI、CLI 等周边功能,专注于提供一个轻量、独立的 AI 引擎库,可轻松集成到任何项目中。
199
+
193
200
  ## 许可证
194
201
 
195
202
  MIT © [lyw405](https://github.com/lyw405)
@@ -1,4 +1,5 @@
1
1
  import type { Provider } from '../core/model';
2
+ import { type ValidationResult } from './configValidation';
2
3
  export type McpStdioServerConfig = {
3
4
  type: 'stdio';
4
5
  command: string;
@@ -93,8 +94,27 @@ export declare class ConfigManager {
93
94
  argvConfig: Partial<Config>;
94
95
  globalConfigPath: string;
95
96
  projectConfigPath: string;
97
+ private validationEnabled;
96
98
  constructor(cwd: string, productName: string, argvConfig: Partial<Config>);
97
99
  get config(): Config;
100
+ /**
101
+ * Enable configuration validation
102
+ * When enabled, invalid configurations will throw errors
103
+ */
104
+ enableValidation(): void;
105
+ /**
106
+ * Disable configuration validation
107
+ */
108
+ disableValidation(): void;
109
+ /**
110
+ * Validate the current configuration
111
+ * Returns validation result without throwing
112
+ */
113
+ validate(): ValidationResult;
114
+ /**
115
+ * Get validation errors as a formatted string
116
+ */
117
+ getValidationErrors(): string;
98
118
  removeConfig(global: boolean, key: string, values?: string[]): void;
99
119
  addConfig(global: boolean, key: string, values: string[]): void;
100
120
  getConfig(global: boolean, key: string): any;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Configuration Validation
3
+ * Runtime validation for engine configuration
4
+ */
5
+ import type { Config } from './config';
6
+ /**
7
+ * Validation result
8
+ */
9
+ export interface ValidationResult {
10
+ valid: boolean;
11
+ errors: ValidationError[];
12
+ warnings: ValidationWarning[];
13
+ }
14
+ export interface ValidationError {
15
+ path: string;
16
+ message: string;
17
+ value?: any;
18
+ }
19
+ export interface ValidationWarning {
20
+ path: string;
21
+ message: string;
22
+ value?: any;
23
+ }
24
+ /**
25
+ * Validate a configuration object
26
+ */
27
+ export declare function validateConfig(config: Partial<Config>): ValidationResult;
28
+ /**
29
+ * Format validation errors for display
30
+ */
31
+ export declare function formatValidationErrors(result: ValidationResult): string;
32
+ /**
33
+ * Assert that configuration is valid, throws if not
34
+ */
35
+ export declare function assertValidConfig(config: Partial<Config>): void;
@@ -17,8 +17,17 @@ export declare class History {
17
17
  compress(model: ModelInfo): Promise<{
18
18
  compressed: boolean;
19
19
  summary?: undefined;
20
+ fallback?: undefined;
21
+ error?: undefined;
20
22
  } | {
21
23
  compressed: boolean;
22
24
  summary: string;
25
+ fallback: boolean;
26
+ error: string;
27
+ } | {
28
+ compressed: boolean;
29
+ summary: string;
30
+ fallback: boolean;
31
+ error?: undefined;
23
32
  }>;
24
33
  }
@@ -2,6 +2,7 @@ import type { ApprovalMode } from '../core/config';
2
2
  import { History } from '../core/history';
3
3
  import type { NormalizedMessage } from '../core/message';
4
4
  import { Usage } from '../core/usage';
5
+ import type { ModeType } from '../modes/types';
5
6
  export type SessionId = string;
6
7
  export declare class Session {
7
8
  id: SessionId;
@@ -31,11 +32,14 @@ export type SessionConfig = {
31
32
  export declare class SessionConfigManager {
32
33
  logPath: string;
33
34
  config: SessionConfig;
35
+ mode: ModeType | null;
34
36
  constructor(opts: {
35
37
  logPath: string;
36
38
  });
37
39
  load(logPath: string): SessionConfig;
38
- write(): void;
40
+ setMode(mode: ModeType): void;
41
+ getMode(): ModeType | null;
42
+ write(): Promise<void>;
39
43
  }
40
44
  export declare function filterMessages(messages: NormalizedMessage[]): NormalizedMessage[];
41
45
  export declare function loadSessionMessages(opts: {
@@ -0,0 +1,55 @@
1
+ /**
2
+ * File-based lock for concurrent write safety
3
+ * Uses lock files with unique identifiers to prevent race conditions
4
+ */
5
+ export declare class FileLock {
6
+ private lockFilePath;
7
+ private lockId;
8
+ private acquired;
9
+ constructor(filePath: string);
10
+ /**
11
+ * Attempt to acquire the lock
12
+ * @returns true if lock was acquired, false otherwise
13
+ */
14
+ acquire(): Promise<boolean>;
15
+ /**
16
+ * Release the lock
17
+ * Only releases if this instance holds the lock (matching lockId)
18
+ */
19
+ release(): void;
20
+ /**
21
+ * Execute a callback while holding the lock
22
+ * Automatically acquires and releases the lock
23
+ */
24
+ withLock<T>(callback: () => T | Promise<T>): Promise<T>;
25
+ /**
26
+ * Read and parse the lock file
27
+ */
28
+ private readLockFile;
29
+ /**
30
+ * Check if a lock has expired (older than timeout)
31
+ */
32
+ private isLockExpired;
33
+ /**
34
+ * Force release any existing lock (use with caution)
35
+ * This can be used to recover from stale locks
36
+ */
37
+ forceRelease(): void;
38
+ }
39
+ /**
40
+ * Global lock registry to manage multiple locks
41
+ * Helps prevent deadlocks and track active locks
42
+ */
43
+ declare class LockRegistry {
44
+ private locks;
45
+ /**
46
+ * Get or create a lock for a file path
47
+ */
48
+ getLock(filePath: string): FileLock;
49
+ /**
50
+ * Release all locks (useful for cleanup)
51
+ */
52
+ releaseAll(): void;
53
+ }
54
+ export declare const lockRegistry: LockRegistry;
55
+ export {};
@@ -0,0 +1,51 @@
1
+ /**
2
+ * PDF Parser Utility
3
+ * Extracts text content from PDF files
4
+ */
5
+ export interface ParseResult {
6
+ text: string;
7
+ pageCount: number;
8
+ metadata?: {
9
+ title?: string;
10
+ author?: string;
11
+ subject?: string;
12
+ keywords?: string;
13
+ creator?: string;
14
+ producer?: string;
15
+ creationDate?: string;
16
+ modificationDate?: string;
17
+ };
18
+ }
19
+ export interface ParseOptions {
20
+ /**
21
+ * Maximum number of pages to parse (0 = all pages)
22
+ * @default 0
23
+ */
24
+ maxPages?: number;
25
+ /**
26
+ * Maximum text length per page (0 = no limit)
27
+ * @default 10000
28
+ */
29
+ maxCharsPerPage?: number;
30
+ /**
31
+ * Include metadata in the result
32
+ * @default true
33
+ */
34
+ includeMetadata?: boolean;
35
+ }
36
+ /**
37
+ * Parse a PDF file and extract text content
38
+ *
39
+ * @param filePath - Absolute path to the PDF file
40
+ * @param options - Parsing options
41
+ * @returns Parsed PDF content
42
+ */
43
+ export declare function parsePDF(filePath: string, options?: ParseOptions): Promise<ParseResult>;
44
+ /**
45
+ * Check if PDF parsing is available (pdf-parse is installed)
46
+ */
47
+ export declare function isPDFParsingAvailable(): Promise<boolean>;
48
+ /**
49
+ * Format PDF parse result for LLM consumption
50
+ */
51
+ export declare function formatPDFResult(result: ParseResult): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oricore",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "OriCore - A powerful AI engine with multi-modal support, tool calling, and extensible architecture",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -78,7 +78,8 @@
78
78
  "tar": "^7.5.2",
79
79
  "turndown": "^7.2.2",
80
80
  "ws": "^8.18.3",
81
- "zod": "^4.1.13"
81
+ "zod": "^4.1.13",
82
+ "jsonrepair": "^3.10.0"
82
83
  },
83
84
  "devDependencies": {
84
85
  "@eslint/js": "^9.39.2",
package/src/core/loop.ts CHANGED
@@ -545,6 +545,41 @@ export async function runLoop(opts: RunLoopOpts): Promise<LoopResult> {
545
545
  input: Record<string, any>;
546
546
  result: ToolResult;
547
547
  }[] = [];
548
+
549
+ // Helper function to add denied results for unprocessed tools
550
+ const addDeniedResultsForRemainingTools = async () => {
551
+ const processedToolCallIds = new Set(
552
+ toolResults.map((tr) => tr.toolCallId),
553
+ );
554
+ for (const remainingToolCall of toolCalls) {
555
+ if (!processedToolCallIds.has(remainingToolCall.toolCallId)) {
556
+ const remainingToolUse: ToolUse = {
557
+ name: remainingToolCall.toolName,
558
+ params: safeParseJson(remainingToolCall.input),
559
+ callId: remainingToolCall.toolCallId,
560
+ };
561
+ let remainingToolResult: ToolResult = {
562
+ llmContent:
563
+ 'Error: Tool execution was skipped due to previous tool denial.',
564
+ isError: true,
565
+ };
566
+ if (opts.onToolResult) {
567
+ remainingToolResult = await opts.onToolResult(
568
+ remainingToolUse,
569
+ remainingToolResult,
570
+ false,
571
+ );
572
+ }
573
+ toolResults.push({
574
+ toolCallId: remainingToolCall.toolCallId,
575
+ toolName: remainingToolCall.toolName,
576
+ input: safeParseJson(remainingToolCall.input),
577
+ result: remainingToolResult,
578
+ });
579
+ }
580
+ }
581
+ };
582
+
548
583
  for (const toolCall of toolCalls) {
549
584
  let toolUse: ToolUse = {
550
585
  name: toolCall.toolName,
@@ -609,6 +644,9 @@ export async function runLoop(opts: RunLoopOpts): Promise<LoopResult> {
609
644
  result: toolResult,
610
645
  });
611
646
 
647
+ // Add denied results for remaining unprocessed tools
648
+ await addDeniedResultsForRemainingTools();
649
+
612
650
  if (!denyReason) {
613
651
  await history.addMessage({
614
652
  role: 'tool',
package/src/core/model.ts CHANGED
@@ -1812,8 +1812,18 @@ export const providers: ProvidersMap = {
1812
1812
  doc: 'https://cerebras.ai/docs',
1813
1813
  models: {
1814
1814
  'zai-glm-4.6': models['glm-4.6'],
1815
- 'zai-glm-4.7': models['glm-4.7'],
1816
- 'gpt-oss-120b': models['gpt-oss-120b'],
1815
+ 'zai-glm-4.7': {
1816
+ ...models['glm-4.7'],
1817
+ // ref: https://inference-docs.cerebras.ai/models/zai-glm-47
1818
+ // default use the context of free tier
1819
+ limit: { context: 64000, output: 40000 },
1820
+ },
1821
+ 'gpt-oss-120b': {
1822
+ ...models['gpt-oss-120b'],
1823
+ // ref: https://inference-docs.cerebras.ai/models/openai-oss
1824
+ // default use the context of free tier
1825
+ limit: { context: 65000, output: 32000 },
1826
+ },
1817
1827
  },
1818
1828
  createModel(name, provider) {
1819
1829
  const apiKey = getProviderApiKey(provider);
package/src/mcp/mcp.ts CHANGED
@@ -335,10 +335,27 @@ export class MCPManager {
335
335
  '@ai-sdk/mcp/mcp-stdio'
336
336
  );
337
337
 
338
+ // Windows: if command is npx/npm/yarn/pnpm/bun/bunx, use cmd.exe to execute, fix: spawn npx ENOENT error
339
+ const windowsShellCommands = [
340
+ 'npx',
341
+ 'npm',
342
+ 'yarn',
343
+ 'pnpm',
344
+ 'bun',
345
+ 'bunx',
346
+ ];
347
+ let command = config.command;
348
+ let args = config.args;
349
+ const isWin = process.platform === 'win32';
350
+ if (isWin && windowsShellCommands.includes(command.toLowerCase())) {
351
+ args = ['/c', command, ...(args || [])];
352
+ command = 'cmd.exe';
353
+ }
354
+
338
355
  return experimental_createMCPClient({
339
356
  transport: new Experimental_StdioMCPTransport({
340
- command: config.command,
341
- args: config.args,
357
+ command,
358
+ args,
342
359
  stderr: 'ignore',
343
360
  env,
344
361
  }),
@@ -1,7 +1,20 @@
1
+ import createDebug from 'debug';
2
+ import { jsonrepair } from 'jsonrepair';
3
+
4
+ const debug = createDebug('oricore:utils:safeParseJson');
5
+
1
6
  export function safeParseJson(json: string) {
2
7
  try {
3
8
  return JSON.parse(json);
4
9
  } catch (_error) {
5
- return {};
10
+ // try to repair the json
11
+ try {
12
+ debug('safeParseJson failed, trying to repair', _error);
13
+ const repairedJson = jsonrepair(json);
14
+ return JSON.parse(repairedJson);
15
+ } catch (_repairError) {
16
+ debug('safeParseJson failed, repair failed', _repairError);
17
+ return {};
18
+ }
6
19
  }
7
20
  }