Skip to content

Commit daf2c75

Browse files
committed
refactor(core): update version injection fallback mechanism and improve type safety
The version injection mechanism has been updated to provide better fallback support when the build-time version is not available. The implementation now includes proper ESM compatibility and error handling for direct execution scenarios. Additionally, import ordering has been standardized across multiple files and type safety has been improved by updating parameter names and interface definitions to align with current API specifications. The OpenAI protocol handler has been updated to handle both args_delta and argsDelta naming conventions for tool call chunks, ensuring compatibility with different client implementations. BREAKING CHANGE: The waitUntilReadyOrFailed method now uses 'callback' parameter instead of 'beforeCheck' for consistency. 更新了版本注入机制,提供了更好的回退支持,当构建时版本不可用时。 实现现在包括适当的ESM兼容性和直接执行场景的错误处理。 此外,多个文件的导入顺序已标准化,通过更新参数名称和接口定义 以与当前API规范保持一致来提高类型安全性。 OpenAI协议处理程序已更新以处理工具调用块的args_delta和argsDelta 命名约定,确保与不同客户端实现的兼容性。 重大变更: waitUntilReadyOrFailed方法现在使用'callback'参数 而不是'beforeCheck'以保持一致性。 Change-Id: Ibd2d266346f9e9f717ce15ad40bb0f79b3fa1741 Signed-off-by: OhYee <oyohyee@oyohyee.com>
1 parent d637900 commit daf2c75

19 files changed

Lines changed: 3412 additions & 163 deletions

File tree

‎examples/mastra.ts‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Agent } from '@mastra/core/agent';
2+
import { AgentRunServer, type AgentRequest } from '../src/server';
3+
import {
4+
MastraConverter,
5+
type AgentEventItem,
6+
model,
7+
toolset,
8+
} from '../src/integration/mastra';
9+
10+
import { logger } from '../src/utils/log';
11+
12+
const mastraAgent = new Agent({
13+
id: 'run_agent',
14+
name: 'AgentRun',
15+
instructions: `
16+
你是一个智能助手,你会帮助用户完成各种任务。你的输出后,必须是返向输出的。
17+
18+
如,用户输入 “你好”,应该输出 “?么的您助帮以可么什有,好您”
19+
`.trim(),
20+
model: () => model({ name: 'ohyee-test' }),
21+
tools: () => toolset({ name: 'start-mcp-time-ggda' }),
22+
});
23+
24+
async function* invokeAgent(
25+
request: AgentRequest,
26+
): AsyncGenerator<AgentEventItem> {
27+
const converter = new MastraConverter();
28+
const mastraStream = await mastraAgent.stream(
29+
request.messages.map(
30+
(msg) =>
31+
({
32+
role: msg.role,
33+
content: msg.content || '',
34+
}) as any,
35+
),
36+
);
37+
for await (const chunk of mastraStream.fullStream) {
38+
const events = converter.convert(chunk);
39+
40+
for (const event of events) {
41+
yield event;
42+
}
43+
}
44+
}
45+
46+
const server = new AgentRunServer({
47+
invokeAgent,
48+
config: { corsOrigins: ['*'] },
49+
});
50+
51+
logger.info(`
52+
curl http://127.0.0.1:9000/openai/v1/chat/completions -X POST \\
53+
-H "Content-Type: application/json" \\
54+
-d \'{"messages": [{"role": "user", "content": "Hello!"}], "stream": true}\'
55+
56+
curl http://127.0.0.1:9000/ag-ui/agent -X POST \\
57+
-H "Content-Type: application/json" \\
58+
-d \'{"messages": [{"role": "user", "content": "Hello!"}]}\'
59+
`);
60+
61+
server.start({ port: 9000 });

‎examples/model.ts‎

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
* npm run example:model
1414
*/
1515

16-
import { ModelClient, ResourceAlreadyExistError, ResourceNotExistError, Status, BackendType, ModelType, ModelService, ModelProxy } from '../src/index';
17-
import type { ModelServiceCreateInput, ModelServiceUpdateInput, ModelProxyCreateInput, ModelProxyUpdateInput, ProviderSettings, ProxyConfig } from '../src/index';
18-
import { logger } from '../src/utils/log';
16+
import type { ModelProxyCreateInput, ModelServiceCreateInput, ProviderSettings, ProxyConfig } from '../src/index';
17+
import { ModelClient, ModelProxy, ModelService, ModelType, ResourceAlreadyExistError, ResourceNotExistError, Status } from '../src/index';
1918
import { Config } from '../src/utils/config';
19+
import { logger } from '../src/utils/log';
2020

2121
// Logger helper
2222
function log(message: string, ...args: unknown[]) {
@@ -68,7 +68,7 @@ async function createOrGetModelService(): Promise<ModelService> {
6868

6969
// 等待就绪 / Wait for ready
7070
await ms.waitUntilReadyOrFailed({
71-
beforeCheck: (service: ModelService) =>
71+
callback: (service) =>
7272
log(` 当前状态 / Current status: ${service.status}`),
7373
});
7474

@@ -112,7 +112,11 @@ async function updateModelService(ms: ModelService): Promise<void> {
112112
async function listModelServices(): Promise<void> {
113113
log('枚举资源列表 / Listing resources');
114114

115-
const services = await ModelService.list({ modelType: ModelType.LLM });
115+
const services = await ModelService.list({
116+
input: {
117+
modelType: ModelType.LLM
118+
}
119+
});
116120
log(
117121
`共有 ${services.length} 个资源,分别为 / Total ${services.length} resources:`,
118122
services.map((s) => s.modelServiceName)
@@ -131,8 +135,10 @@ async function invokeModelService(ms: ModelService): Promise<void> {
131135
});
132136

133137
// 流式输出 / Stream output
134-
for await (const chunk of result.textStream) {
135-
process.stdout.write(chunk);
138+
if ('textStream' in result && result.textStream) {
139+
for await (const chunk of result.textStream) {
140+
process.stdout.write(chunk);
141+
}
136142
}
137143
logger.info(''); // 换行
138144
}
@@ -198,7 +204,7 @@ async function createOrGetModelProxy(): Promise<ModelProxy> {
198204

199205
// 等待就绪 / Wait for ready
200206
await mp.waitUntilReadyOrFailed({
201-
beforeCheck: (proxy: ModelProxy) =>
207+
callback: (proxy) =>
202208
log(` 当前状态 / Current status: ${proxy.status}`),
203209
});
204210

@@ -257,14 +263,16 @@ async function listModelProxies(): Promise<void> {
257263
async function invokeModelProxy(mp: ModelProxy): Promise<void> {
258264
log('调用模型代理进行推理 / Invoking model proxy for inference');
259265

260-
const result = await mp.completions({
266+
const result = await mp.completion({
261267
messages: [{ role: 'user', content: '你好,请介绍一下你自己' }],
262268
stream: true,
263269
});
264270

265271
// 流式输出 / Stream output
266-
for await (const chunk of result.textStream) {
267-
process.stdout.write(chunk);
272+
if ('textStream' in result && result.textStream) {
273+
for await (const chunk of result.textStream) {
274+
process.stdout.write(chunk);
275+
}
268276
}
269277
logger.info(''); // 换行
270278
}

‎package.json‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
"README.md"
5959
],
6060
"scripts": {
61-
"build": "npm run generate-exports && tsup",
61+
"prebuild": "npm run generate-exports",
62+
"build": "tsup",
6263
"build:types": "tsc -p tsconfig.types.json",
6364
"codegen": "npx tsx scripts/codegen.ts",
6465
"generate-exports": "node scripts/generate-exports.mjs",
@@ -114,6 +115,14 @@
114115
"uuid": "^9.0.0",
115116
"zod": "^4.2.1"
116117
},
118+
"peerDependencies": {
119+
"@mastra/core": "*"
120+
},
121+
"peerDependenciesMeta": {
122+
"@mastra/core": {
123+
"optional": true
124+
}
125+
},
117126
"devDependencies": {
118127
"@happy-dom/global-registrator": "^15.0.0",
119128
"@mastra/core": "^1.0.0",

‎src/index.ts‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,28 @@
1010

1111
// Version is injected at build time from package.json
1212
declare const __VERSION__: string;
13-
export const VERSION = __VERSION__;
13+
export const VERSION = (() => {
14+
// Use injected version if available (build time)
15+
if (typeof __VERSION__ !== 'undefined') {
16+
return __VERSION__;
17+
}
18+
19+
// Fallback to package.json version for direct execution
20+
try {
21+
// In ESM, we need to use import.meta.url to get the current file path
22+
const { readFileSync } = require('fs');
23+
const { fileURLToPath } = require('url');
24+
const { dirname, join } = require('path');
25+
26+
const currentFile = fileURLToPath(import.meta.url);
27+
const packageJsonPath = join(dirname(currentFile), '../../package.json');
28+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
29+
return packageJson.version;
30+
} catch {
31+
// Final fallback
32+
return '0.0.0-dev';
33+
}
34+
})();
1435

1536
// Config
1637
export { Config, type ConfigOptions } from './utils/config';

‎src/integration/mastra/converter.ts‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,6 @@ import { EventType, type AgentEvent } from '@/server/core/model';
3030
import { type ChunkType } from '@mastra/core/stream';
3131
import { logger } from '@/utils/log';
3232

33-
// Mastra ChunkType definition
34-
// We define a minimal interface here to avoid direct dependency on @mastra/core
35-
// Users should have @mastra/core installed separately
36-
interface MastraChunkBase {
37-
type: string;
38-
runId: string;
39-
from: string;
40-
payload?: Record<string, unknown>;
41-
}
42-
4333
/**
4434
* Agent event item - can be a string (text) or AgentEvent (structured event)
4535
* Agent 事件项 - 可以是字符串(文本)或 AgentEvent(结构化事件)

‎src/integration/mastra/index.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
1616
import type { LanguageModelV3 } from '@ai-sdk/provider';
1717
import { fromJSONSchema } from 'zod';
1818

19-
import type { ToolAction, ToolExecutionContext } from '@mastra/core/tools';
2019
import type { ToolsInput } from '@mastra/core/agent';
20+
import type { ToolAction, ToolExecutionContext } from '@mastra/core/tools';
2121

2222
import {
23-
toolset as builtinToolset,
2423
model as builtinModel,
24+
toolset as builtinToolset,
2525
sandboxToolset,
26-
type CommonToolSet,
2726
type CanonicalTool,
27+
type CommonToolSet,
2828
} from '../builtin';
2929

3030
/**
@@ -143,7 +143,6 @@ export async function createMastraTool<
143143
ToolAction<TSchemaIn, TSchemaOut, TSuspend, TResume, TContext, TId>
144144
> {
145145
const { createTool } = await import('@mastra/core/tools');
146-
// @ts-expect-error - Type mismatch with Mastra's createTool
147146
return await createTool(params);
148147
}
149148

‎src/server/protocol/openai.ts‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,9 @@ export class OpenAIProtocolHandler extends ProtocolHandler {
153153
content: msg.content as string | undefined,
154154
name: msg.name as string | undefined,
155155
toolCallId: msg.tool_call_id as string | undefined,
156-
toolCalls: msg.tool_calls
157-
? (msg.tool_calls as unknown[]).map((tc) => {
156+
toolCalls:
157+
msg.tool_calls ?
158+
(msg.tool_calls as unknown[]).map((tc) => {
158159
const call = tc as Record<string, unknown>;
159160
return {
160161
id: call.id as string,
@@ -194,7 +195,10 @@ export class OpenAIProtocolHandler extends ProtocolHandler {
194195
let sentRole = false;
195196
let hasText = false;
196197
let toolCallIndex = -1;
197-
const toolCallStates = new Map<string, { index: number; started: boolean }>();
198+
const toolCallStates = new Map<
199+
string,
200+
{ index: number; started: boolean }
201+
>();
198202
let hasToolCalls = false;
199203

200204
for await (const event of events) {
@@ -230,7 +234,9 @@ export class OpenAIProtocolHandler extends ProtocolHandler {
230234
if (event.event === EventType.TOOL_CALL_CHUNK) {
231235
const toolId = event.data?.id as string;
232236
const toolName = event.data?.name as string;
233-
const argsDelta = event.data?.args_delta as string;
237+
const argsDelta =
238+
(event.data?.args_delta as string) ||
239+
(event.data?.argsDelta as string);
234240

235241
// First time seeing this tool call
236242
if (toolId && !toolCallStates.has(toolId)) {
@@ -283,7 +289,10 @@ export class OpenAIProtocolHandler extends ProtocolHandler {
283289
}
284290

285291
// Send finish_reason
286-
const finishReason = hasToolCalls ? 'tool_calls' : hasText ? 'stop' : 'stop';
292+
const finishReason =
293+
hasToolCalls ? 'tool_calls'
294+
: hasText ? 'stop'
295+
: 'stop';
287296
yield this.buildChunk(context, { delta: {}, finish_reason: finishReason });
288297

289298
// Send [DONE]
@@ -332,7 +341,9 @@ export class OpenAIProtocolHandler extends ProtocolHandler {
332341
} else if (event.event === EventType.TOOL_CALL_CHUNK) {
333342
const toolId = event.data?.id as string;
334343
const toolName = event.data?.name as string;
335-
const argsDelta = event.data?.args_delta as string;
344+
const argsDelta =
345+
(event.data?.args_delta as string) ||
346+
(event.data?.argsDelta as string);
336347

337348
// Find or create tool call
338349
let toolCall = toolCalls.find((tc) => tc.id === toolId);

‎src/utils/control-api.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
* This module defines the base class for control API.
66
*/
77

8-
import * as $AgentRun from "@alicloud/agentrun20250910";
9-
import * as $OpenApi from "@alicloud/openapi-client";
8+
import * as $AgentRun from '@alicloud/agentrun20250910';
9+
import * as $OpenApi from '@alicloud/openapi-client';
1010

11-
import { Config } from "./config";
11+
import { Config } from './config';
1212

1313
// Handle ESM/CJS interop for Client class
1414
const $AgentRunClient =
@@ -38,8 +38,8 @@ export class ControlAPI {
3838
let endpoint = cfg.controlEndpoint;
3939

4040
// Remove protocol prefix
41-
if (endpoint.startsWith("http://") || endpoint.startsWith("https://")) {
42-
endpoint = endpoint.split("://")[1];
41+
if (endpoint.startsWith('http://') || endpoint.startsWith('https://')) {
42+
endpoint = endpoint.split('://')[1];
4343
}
4444

4545
const openApiConfig = new $OpenApi.Config({
@@ -49,6 +49,7 @@ export class ControlAPI {
4949
regionId: cfg.regionId,
5050
endpoint: endpoint,
5151
connectTimeout: cfg.timeout,
52+
readTimeout: cfg.readTimeout,
5253
});
5354

5455
return new $AgentRunClient(openApiConfig);

‎tests/e2e/model/model.test.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,10 @@ describe('Model E2E Tests', () => {
118118
// 验证时间戳
119119
expect(modelService.createdAt).toBeDefined();
120120
const createdAt = new Date(modelService.createdAt!);
121-
expect(createdAt.getTime()).toBeGreaterThan(time1.getTime());
122-
expect(createdAt.getTime()).toBeLessThan(time2.getTime());
121+
expect(createdAt.getTime()).toBeGreaterThanOrEqual(time1.getTime());
122+
expect(createdAt.getTime()).toBeLessThanOrEqual(
123+
time2.getTime() + 5 * 60 * 1000
124+
);
123125
});
124126

125127
it('should get a ModelService by name', async () => {
@@ -303,8 +305,10 @@ describe('Model E2E Tests', () => {
303305
// 验证时间戳
304306
expect(modelProxy.createdAt).toBeDefined();
305307
const createdAt = new Date(modelProxy.createdAt!);
306-
expect(createdAt.getTime()).toBeGreaterThan(time1.getTime());
307-
expect(createdAt.getTime()).toBeLessThan(time2.getTime());
308+
expect(createdAt.getTime()).toBeGreaterThanOrEqual(time1.getTime());
309+
expect(createdAt.getTime()).toBeLessThanOrEqual(
310+
time2.getTime() + 5 * 60 * 1000
311+
);
308312
} catch (error) {
309313
// 如果因为 executionRole 问题失败,跳过
310314
logger.warn(

0 commit comments

Comments
 (0)