@vectorx/functions-framework 0.8.0 → 1.0.0

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
@@ -136,6 +136,34 @@ npm test
136
136
  npm run build
137
137
  ```
138
138
 
139
-
140
139
  ### 环境管理
141
140
  读取 .env 文件,校验 env 文件的可用性,获取当前运行环境
141
+
142
+
143
+ ## rcb-ff 自动检测项目类型
144
+
145
+ 当直接使用 `rcb-ff` 命令启动服务时,如果未指定 `--mode` 参数,会自动检测项目类型:
146
+
147
+ ### 检测规则
148
+
149
+ 1. **Agent 项目检测**:
150
+ - 如果存在 `project.config.json` 文件且包含 `agentId` 字段,则判定为 `agent` 项目
151
+
152
+ 2. **Fun 项目检测**:
153
+ - 如果不存在 `project.config.json` 或 `project.config.json` 中不存在 `agentId` 字段
154
+ - 且存在 `agent-cloudbase-functions.json` 配置文件
155
+ - 则判定为 `fun` 项目
156
+
157
+ 3. **默认行为**:
158
+ - 如果无法确定项目类型,默认使用 `agent` 模式(保持向后兼容)
159
+
160
+ ### 使用示例
161
+
162
+ ```bash
163
+ # 自动检测项目类型(推荐)
164
+ rcb-ff --directory ./my-project
165
+
166
+ # 明确指定模式(覆盖自动检测)
167
+ rcb-ff --directory ./my-project --mode fun
168
+ rcb-ff --directory ./my-project --mode agent
169
+ ```
package/bin/rcb-ff.js CHANGED
@@ -7,6 +7,40 @@ const fs = require('fs');
7
7
  const nodemon = require('nodemon');
8
8
  const chalk = require('chalk');
9
9
 
10
+ /**
11
+ * 自动检测项目类型(agent 或 fun)
12
+ * 参考 fun-project-validator.ts 的检测逻辑
13
+ * @param {string} workDir 工作目录
14
+ * @returns {string} 'agent' | 'fun'
15
+ */
16
+ function detectProjectMode(workDir) {
17
+ const projectConfigPath = path.join(workDir, 'project.config.json');
18
+
19
+ // 检查是否存在 project.config.json 且包含 agentId
20
+ if (fs.existsSync(projectConfigPath)) {
21
+ try {
22
+ const projectConfig = JSON.parse(fs.readFileSync(projectConfigPath, 'utf-8'));
23
+ if (projectConfig.agentId) {
24
+ // 存在 agentId,判定为 agent 项目
25
+ return 'agent';
26
+ }
27
+ } catch (error) {
28
+ // project.config.json 解析失败,继续检测
29
+ }
30
+ }
31
+
32
+ // 检查是否存在 agent-cloudbase-functions.json
33
+ const functionsConfigPath = path.join(workDir, 'agent-cloudbase-functions.json');
34
+ if (fs.existsSync(functionsConfigPath)) {
35
+ // 不存在 project.config.json 或不存在 agentId,但存在 agent-cloudbase-functions.json
36
+ // 判定为 fun 项目
37
+ return 'fun';
38
+ }
39
+
40
+ // 默认返回 agent(保持向后兼容)
41
+ return 'agent';
42
+ }
43
+
10
44
  const program = new Command();
11
45
 
12
46
  program
@@ -20,13 +54,22 @@ program
20
54
  .option('--enable-cors', '启用 CORS')
21
55
  .option('--allowed-origins <origins>', 'CORS 允许的域名,用逗号分隔', 'localhost,127.0.0.1')
22
56
  .option('--config <path>', '指定配置文件路径', 'agent-cloudbase-functions.json')
57
+ .option('--mode <mode>', '运行模式: agent | fun(未指定时自动检测)')
23
58
  .option('--enableRedLangfuse', '启用 Red Langfuse 上报')
59
+ .option('--verbose', '输出详细错误堆栈(用于排查问题)')
24
60
  .option('-w, --watch', '启用文件监听模式,文件变化时自动重启服务');
25
61
 
26
62
  program.parse();
27
63
 
28
64
  const options = program.opts();
29
65
 
66
+ // 如果未明确指定 mode,则自动检测
67
+ if (!options.mode) {
68
+ const detectedMode = detectProjectMode(path.resolve(options.directory));
69
+ options.mode = detectedMode;
70
+ console.log(chalk.dim(`[自动检测] 项目类型: ${detectedMode}`));
71
+ }
72
+
30
73
  async function startServer() {
31
74
  try {
32
75
  // 解析并验证目录路径
@@ -46,6 +89,7 @@ async function startServer() {
46
89
  functionsConfigFile: path.resolve(directory, options.config),
47
90
  enableCors: options.enableCors,
48
91
  allowedOrigins: options.allowedOrigins.split(','),
92
+ mode: options.mode === 'fun' ? 'fun' : 'agent',
49
93
  redLangfuseConfig: {
50
94
  enable: Boolean(options.enableRedLangfuse),
51
95
  },
@@ -74,6 +118,7 @@ async function startServer() {
74
118
  // 使用独立的控制台实例打印启动信息
75
119
  startupConsole.log('\n\n' + chalk.bold.cyan(' === RCB Functions Framework ==='));
76
120
  startupConsole.log(`${chalk.green('✔')} ${chalk.bold('状态')}: ${chalk.green('运行中')}`);
121
+ startupConsole.log(`${chalk.green('✔')} ${chalk.bold('模式')}: ${chalk.yellow(frameworkOptions.mode)}`);
77
122
  startupConsole.log(`${chalk.green('✔')} ${chalk.bold('端口')}: ${chalk.yellow(frameworkOptions.port)}`);
78
123
  startupConsole.log(`${chalk.green('✔')} ${chalk.bold('URL')}: ${chalk.blue.underline(`http://localhost:${frameworkOptions.port}`)}`);
79
124
  startupConsole.log(`${chalk.green('✔')} ${chalk.bold('目标函数')}: ${chalk.yellow(frameworkOptions.target)}`);
@@ -111,7 +156,14 @@ async function startServer() {
111
156
  });
112
157
 
113
158
  } catch (err) {
114
- console.error('启动服务器失败:', err);
159
+ // 默认只输出友好错误信息(避免堆栈刷屏);需要排查时可通过 --verbose 输出完整堆栈
160
+ const message = (err && err.message) ? err.message : String(err);
161
+ console.error('启动服务器失败:');
162
+ console.error(message);
163
+ if (options.verbose) {
164
+ const stack = (err && err.stack) ? err.stack : '';
165
+ if (stack) console.error(stack);
166
+ }
115
167
  process.exit(1);
116
168
  }
117
169
  }
package/lib/config.js CHANGED
@@ -1,10 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getBaseUrl = getBaseUrl;
4
- const ENV_BASE_URL = {
5
- development: "https://agentbase-sandbox.xiaohongshu.com",
6
- production: "https://agentbase.xiaohongshu.com",
7
- };
4
+ const endpoints_1 = require("@vectorx/endpoints");
8
5
  function getBaseUrl() {
9
- return ENV_BASE_URL[process.env.OPEN_PLATFORM_STAGE] || ENV_BASE_URL.development;
6
+ return (0, endpoints_1.resolveUrl)("@vectorx/functions-framework", "baseUrl");
10
7
  }
package/lib/constants.js CHANGED
@@ -7,4 +7,5 @@ exports.defaultOptions = {
7
7
  keepAliveTimeoutMS: 25000,
8
8
  functionsConfigFile: "agent-cloudbase-functions.json",
9
9
  enableCors: false,
10
+ mode: "agent",
10
11
  };
package/lib/framework.js CHANGED
@@ -73,8 +73,9 @@ class AgentServerFramework {
73
73
  }
74
74
  startServer() {
75
75
  return __awaiter(this, void 0, void 0, function* () {
76
- const { functionsConfigFile } = this.options;
76
+ const { functionsConfigFile, mode = "agent" } = this.options;
77
77
  let cloudFunctionConfig;
78
+ let projectConfig;
78
79
  if (!process.env.OPEN_PLATFORM_STAGE) {
79
80
  process.stdout.write(chalk_1.default.yellow(`未设置 OPEN_PLATFORM_STAGE 环境变量,使用默认值: development\n`));
80
81
  process.env.OPEN_PLATFORM_STAGE = "development";
@@ -100,9 +101,27 @@ class AgentServerFramework {
100
101
  }
101
102
  });
102
103
  }
103
- const projectConfigPath = path_1.default.join(process.cwd(), "project.config.json");
104
- if (!(0, fs_1.existsSync)(projectConfigPath)) {
105
- throw new Error(`
104
+ if (mode === "agent") {
105
+ ({ projectConfig, cloudFunctionConfig } = this.loadAgentModeConfigs(functionsConfigFile));
106
+ }
107
+ else {
108
+ ({ projectConfig, cloudFunctionConfig } = this.loadFunModeConfigs(functionsConfigFile));
109
+ }
110
+ this.router = new router_1.Router(cloudFunctionConfig);
111
+ const server = (0, server_1.createServer)(this.options, this.router, projectConfig);
112
+ return server;
113
+ });
114
+ }
115
+ getRegisteredFunction(fnName) {
116
+ const fnId = (0, function_registry_1.buildFnId)("@fn", fnName);
117
+ return (0, function_registry_1.getRegisteredFunction)(fnId);
118
+ }
119
+ loadAgentModeConfigs(functionsConfigFile) {
120
+ const projectConfigPath = path_1.default.join(process.cwd(), "project.config.json");
121
+ if (!(0, fs_1.existsSync)(projectConfigPath)) {
122
+ const funConfigPath = path_1.default.join(process.cwd(), "agent-cloudbase-functions.json");
123
+ const maybeFunProject = (0, fs_1.existsSync)(funConfigPath);
124
+ throw new Error(`
106
125
  ❌ 项目配置文件未找到: ${projectConfigPath}
107
126
 
108
127
  请确保:
@@ -113,11 +132,23 @@ class AgentServerFramework {
113
132
  "version": "0.0.1",
114
133
  "desc": "发布示例",
115
134
  }
116
- `);
117
- }
118
- const projectConfig = require(projectConfigPath);
119
- if (!(0, fs_1.existsSync)(functionsConfigFile)) {
120
- throw new Error(`
135
+ ${maybeFunProject
136
+ ? `
137
+ 💡 诊断提示:
138
+ - 检测到当前目录存在云函数配置文件(agent-cloudbase-functions.json)
139
+ - 这可能是一个“云函数(fun)项目”目录,但你使用了“智能体(agent)模式”启动
140
+
141
+ 建议你:
142
+ - 改用 fun 模式启动(例如:rcb-ff --mode fun ... 或使用 rcb fun dev)
143
+ - 或切换到正确的 Agent 项目目录后再启动
144
+
145
+ 参考文档:https://miniapp.xiaohongshu.com/doc/DC185638
146
+ `
147
+ : ""}`);
148
+ }
149
+ const projectConfig = require(projectConfigPath);
150
+ if (!(0, fs_1.existsSync)(functionsConfigFile)) {
151
+ throw new Error(`
121
152
  ❌ 配置文件未找到: ${functionsConfigFile}
122
153
 
123
154
  请确保:
@@ -135,13 +166,42 @@ class AgentServerFramework {
135
166
  ]
136
167
  }
137
168
  `);
169
+ }
170
+ const cloudFunctionConfig = this.loadAndRegisterFunctions(functionsConfigFile, "agent");
171
+ return { projectConfig, cloudFunctionConfig };
172
+ }
173
+ loadFunModeConfigs(functionsConfigFile) {
174
+ if (!(0, fs_1.existsSync)(functionsConfigFile)) {
175
+ throw new Error(`
176
+ ❌ 函数配置文件未找到: ${functionsConfigFile}
177
+
178
+ 请确保:
179
+ 1. 配置文件 'agent-cloudbase-functions.json' 存在于项目根目录
180
+ 2. 配置文件格式正确,且 functions 字段已正确配置
181
+ `);
182
+ }
183
+ const cloudFunctionConfig = this.loadAndRegisterFunctions(functionsConfigFile, "fun");
184
+ const projectConfig = {
185
+ agentId: cloudFunctionConfig.agentId || "local-fun",
186
+ version: "0.0.0-local",
187
+ desc: "Local functions development (fun mode)",
188
+ name: "local-fun",
189
+ "@vectorx/agent-runtime": getDependencyVersion("@vectorx/agent-runtime", process.cwd()) || "",
190
+ "@vectorx/functions-framework": getDependencyVersion("@vectorx/functions-framework", process.cwd()) || "",
191
+ };
192
+ return { projectConfig, cloudFunctionConfig };
193
+ }
194
+ loadAndRegisterFunctions(functionsConfigFile, mode) {
195
+ try {
196
+ const cloudFunctionConfig = (0, function_loader_1.loadFunctionsConfig)(functionsConfigFile);
197
+ const fns = cloudFunctionConfig.functions || [];
198
+ if (!Array.isArray(fns) || fns.length === 0) {
199
+ throw new Error("functions 配置为空,请至少配置一个函数");
138
200
  }
139
- try {
140
- cloudFunctionConfig = (0, function_loader_1.loadFunctionsConfig)(functionsConfigFile);
141
- for (const fn of cloudFunctionConfig.functions) {
142
- const mainFn = (0, function_loader_1.loadFunction)(cloudFunctionConfig.functionsRoot, fn);
143
- if (!mainFn) {
144
- throw new Error(`
201
+ for (const fn of fns) {
202
+ const mainFn = (0, function_loader_1.loadFunction)(cloudFunctionConfig.functionsRoot, fn);
203
+ if (!mainFn) {
204
+ throw new Error(`
145
205
  ❌ 函数加载失败: ${fn.name}
146
206
 
147
207
  请检查:
@@ -149,14 +209,14 @@ class AgentServerFramework {
149
209
  2. 源文件是否存在: ${fn.source}
150
210
  3. 函数是否正确导出
151
211
  `);
152
- }
153
- (0, function_registry_1.registerFunction)(fn.name, mainFn);
154
212
  }
155
- this.router = new router_1.Router(cloudFunctionConfig);
213
+ (0, function_registry_1.registerFunction)(fn.name, mainFn);
156
214
  }
157
- catch (err) {
158
- throw new Error(`
159
- 函数配置加载失败
215
+ return cloudFunctionConfig;
216
+ }
217
+ catch (err) {
218
+ throw new Error(`
219
+ ❌ 函数配置加载失败(${mode} 模式)
160
220
 
161
221
  错误详情: ${err}
162
222
 
@@ -167,13 +227,7 @@ class AgentServerFramework {
167
227
 
168
228
  配置文件路径: ${functionsConfigFile}
169
229
  `);
170
- }
171
- const server = (0, server_1.createServer)(this.options, this.router, projectConfig);
172
- return server;
173
- });
174
- }
175
- getRegisteredFunction(fnName) {
176
- return (0, function_registry_1.getRegisteredFunction)(fnName);
230
+ }
177
231
  }
178
232
  }
179
233
  function createAgentServerFramework(options) {
@@ -45,7 +45,8 @@ function loadFunction(functionsRoot, functionConfig) {
45
45
  if (!functionConfig.source) {
46
46
  functionConfig.source = exports.DEFAULT_FN_NAME;
47
47
  }
48
- const indexPath = (0, path_1.join)(functionsRoot, functionConfig.source);
48
+ const directory = functionConfig.directory || ".";
49
+ const indexPath = (0, path_1.join)(functionsRoot, directory, functionConfig.source);
49
50
  if (!(0, fs_1.existsSync)(indexPath)) {
50
51
  throw new Error(`Function entry file not found at: ${indexPath}, functionConfig: ${JSON.stringify(functionConfig)}`);
51
52
  }
@@ -50,10 +50,62 @@ const request_1 = require("./request");
50
50
  const sse_1 = require("./sse");
51
51
  const unified_responder_1 = require("./unified-responder");
52
52
  const helper_1 = require("./utils/helper");
53
+ const isPlainObject = (val) => {
54
+ if (!val || typeof val !== "object")
55
+ return false;
56
+ const proto = Object.getPrototypeOf(val);
57
+ return proto === Object.prototype || proto === null;
58
+ };
59
+ const parseCookies = (cookieHeader) => {
60
+ const cookies = {};
61
+ if (!cookieHeader || typeof cookieHeader !== "string")
62
+ return cookies;
63
+ const parts = cookieHeader.split(";");
64
+ for (const part of parts) {
65
+ const s = part.trim();
66
+ if (!s)
67
+ continue;
68
+ const idx = s.indexOf("=");
69
+ if (idx === -1)
70
+ continue;
71
+ const key = s.slice(0, idx).trim();
72
+ const val = s.slice(idx + 1).trim();
73
+ if (!key)
74
+ continue;
75
+ try {
76
+ cookies[key] = decodeURIComponent(val);
77
+ }
78
+ catch (_a) {
79
+ cookies[key] = val;
80
+ }
81
+ }
82
+ return cookies;
83
+ };
84
+ const normalizeQuery = (query) => {
85
+ if (!query || typeof query !== "object")
86
+ return {};
87
+ return Object.assign({}, query);
88
+ };
89
+ const normalizeHeaders = (headers) => {
90
+ if (!headers || typeof headers !== "object")
91
+ return {};
92
+ const out = {};
93
+ for (const [k, v] of Object.entries(headers)) {
94
+ out[String(k).toLowerCase()] = v;
95
+ }
96
+ return out;
97
+ };
98
+ const resolveTrigger = (ctx) => {
99
+ var _a, _b;
100
+ const fromHeader = String(((_b = (_a = ctx.request) === null || _a === void 0 ? void 0 : _a.headers) === null || _b === void 0 ? void 0 : _b["xhs-cloud-trigger"]) || "").toLowerCase();
101
+ if (fromHeader === "online" || fromHeader === "ide_debug" || fromHeader === "console_debug")
102
+ return fromHeader;
103
+ return "online";
104
+ };
53
105
  const wrapEventFunction = (execute, projectConfig) => {
54
106
  return (ctx, next) => __awaiter(void 0, void 0, void 0, function* () {
55
- var _a;
56
- let event;
107
+ var _a, _b, _c, _d, _e;
108
+ let params;
57
109
  if (ctx.request.files) {
58
110
  const filesWithBuffer = {};
59
111
  const fs = yield Promise.resolve().then(() => __importStar(require("fs")));
@@ -79,21 +131,40 @@ const wrapEventFunction = (execute, projectConfig) => {
79
131
  })));
80
132
  filesWithBuffer[key] = Array.isArray(fileOrFiles) ? processedFiles : processedFiles[0];
81
133
  }
82
- event = Object.assign(Object.assign({}, filesWithBuffer), ctx.request.body);
134
+ const fields = isPlainObject(ctx.request.body) ? ctx.request.body : {};
135
+ params = Object.assign(Object.assign({}, fields), filesWithBuffer);
83
136
  }
84
- else if (ctx.request.body) {
85
- event = ctx.request.body;
137
+ else if (ctx.method === "GET" || ctx.method === "HEAD") {
138
+ params = ctx.query || {};
86
139
  }
87
140
  else {
88
- event = ctx.rawBody;
141
+ params = (_a = ctx.request.body) !== null && _a !== void 0 ? _a : ctx.rawBody;
89
142
  }
143
+ if (params === undefined || params === null) {
144
+ params = {};
145
+ }
146
+ const event = { params };
90
147
  let executeDone = false;
91
148
  let hasSwitchSSEMode = false;
149
+ const headers = normalizeHeaders((_b = ctx.request) === null || _b === void 0 ? void 0 : _b.headers);
150
+ const httpMethod = ctx.method;
151
+ const path = ctx.path;
152
+ const url = ctx.url;
153
+ const query = normalizeQuery(ctx.query);
154
+ const cookies = parseCookies(headers.cookie);
155
+ const trigger = resolveTrigger(ctx);
92
156
  const context = {
93
157
  ctxId: ctx.state.eventID,
94
158
  eventID: ctx.state.eventID,
95
159
  eventType: ctx.state.eventType,
96
160
  timestamp: ctx.state.timestamp,
161
+ headers,
162
+ httpMethod,
163
+ path,
164
+ url,
165
+ query,
166
+ cookies,
167
+ trigger,
97
168
  httpContext: ctx.httpContext,
98
169
  baseUrl: (0, config_1.getBaseUrl)(),
99
170
  request: new request_1.Request({
@@ -102,11 +173,26 @@ const wrapEventFunction = (execute, projectConfig) => {
102
173
  restrictedMethods: ["get", "post", "upload", "download"],
103
174
  defaultHeaders: {
104
175
  "agent-id": projectConfig.agentId,
105
- "open-id": ctx.request.headers["open-id"],
176
+ "open-id": ((_d = (_c = ctx.request) === null || _c === void 0 ? void 0 : _c.headers) === null || _d === void 0 ? void 0 : _d["open-id"]) || "UNKNOWN",
106
177
  "x-open-agent-trace-id": ctx.state.eventID,
107
178
  },
108
179
  }),
109
180
  logger: logger_1.functionsLogger,
181
+ set: (field, value) => {
182
+ ctx.set(field, value);
183
+ },
184
+ remove: (field) => {
185
+ ctx.remove(field);
186
+ },
187
+ status: (code) => {
188
+ ctx.status = code;
189
+ },
190
+ setCookie: (name, value, options) => {
191
+ ctx.cookies.set(name, value, options);
192
+ },
193
+ clearCookie: (name, options) => {
194
+ ctx.cookies.set(name, null, Object.assign(Object.assign({}, (options || {})), { expires: new Date(0), maxAge: 0 }));
195
+ },
110
196
  sse: () => {
111
197
  var _a;
112
198
  const sseInstance = (_a = ctx.sse) === null || _a === void 0 ? void 0 : _a.call(ctx);
@@ -127,12 +213,19 @@ const wrapEventFunction = (execute, projectConfig) => {
127
213
  eventID: context.eventID,
128
214
  eventType: context.eventType,
129
215
  timestamp: context.timestamp,
216
+ headers: context.headers,
217
+ httpMethod: context.httpMethod,
218
+ path: context.path,
219
+ url: context.url,
220
+ query: context.query,
221
+ cookies: context.cookies,
222
+ trigger: context.trigger,
130
223
  httpContext: context.httpContext,
131
224
  baseUrl: context.baseUrl,
132
225
  request: context.request,
133
226
  };
134
227
  (0, helper_1.deepFreeze)(contextToFreeze);
135
- const frozenContext = Object.assign(Object.assign({}, contextToFreeze), { logger: context.logger, sse: context.sse });
228
+ const frozenContext = Object.assign(Object.assign({}, contextToFreeze), { logger: context.logger, set: context.set, remove: context.remove, status: context.status, setCookie: context.setCookie, clearCookie: context.clearCookie, sse: context.sse });
136
229
  ctx.state.event = event;
137
230
  ctx.state.contextInjected = frozenContext;
138
231
  let result;
@@ -142,7 +235,7 @@ const wrapEventFunction = (execute, projectConfig) => {
142
235
  catch (e) {
143
236
  ctx.state.error = e;
144
237
  if (hasSwitchSSEMode) {
145
- const sse = (_a = ctx.sse) === null || _a === void 0 ? void 0 : _a.call(ctx);
238
+ const sse = (_e = ctx.sse) === null || _e === void 0 ? void 0 : _e.call(ctx);
146
239
  if (sse && !sse.isClosed) {
147
240
  const errorEvent = {
148
241
  event: "error",
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.apmMiddleware = exports.openGwRequestMiddleware = exports.logsQueryMiddleware = exports.loggerMiddleware = exports.functionRouteMiddleware = exports.eventIdMiddleware = exports.contextInjectionMiddleware = exports.asyncContextMiddleware = void 0;
3
+ exports.apmMiddleware = exports.openGwRequestMiddleware = exports.logsQueryMiddleware = exports.loggerMiddleware = exports.functionRouteMiddleware = exports.eventIdMiddleware = exports.envVarsInjectionMiddleware = exports.contextInjectionMiddleware = exports.asyncContextMiddleware = void 0;
4
4
  var middle_async_context_1 = require("./middle-async-context");
5
5
  Object.defineProperty(exports, "asyncContextMiddleware", { enumerable: true, get: function () { return middle_async_context_1.asyncContextMiddleware; } });
6
6
  var middle_context_injection_1 = require("./middle-context-injection");
7
7
  Object.defineProperty(exports, "contextInjectionMiddleware", { enumerable: true, get: function () { return middle_context_injection_1.contextInjectionMiddleware; } });
8
+ var middle_env_vars_injection_1 = require("./middle-env-vars-injection");
9
+ Object.defineProperty(exports, "envVarsInjectionMiddleware", { enumerable: true, get: function () { return middle_env_vars_injection_1.envVarsInjectionMiddleware; } });
8
10
  var middle_event_id_1 = require("./middle-event-id");
9
11
  Object.defineProperty(exports, "eventIdMiddleware", { enumerable: true, get: function () { return middle_event_id_1.eventIdMiddleware; } });
10
12
  var middle_function_route_1 = require("./middle-function-route");
@@ -44,6 +44,7 @@ function loggerMiddleware() {
44
44
  finally {
45
45
  const endTime = Date.now();
46
46
  const duration = endTime - startTime;
47
+ const contentType = ctx.get("content-type") || "-";
47
48
  const accessLog = {
48
49
  type: "HTTP_ACCESS",
49
50
  method: ctx.method,
@@ -65,7 +66,7 @@ function loggerMiddleware() {
65
66
  asyncContext.userCodeLogsFlushTimer = undefined;
66
67
  }
67
68
  logger_1.functionsLogger.logUserCodelog();
68
- process.stdout.write(chalk_1.default.greenBright(`▶️ ${chalk_1.default.gray(`[${new Date().toLocaleString()} - ${eventId.slice(0, -10)}]`)} - ${duration}ms\n`));
69
+ process.stdout.write(chalk_1.default.greenBright(`▶️ ${chalk_1.default.gray(`[${new Date().toLocaleString()} - ${eventId.slice(0, -10)}]`)} ${chalk_1.default.bold(`${ctx.method} ${ctx.path}`)} ${chalk_1.default.bold(`[${contentType}]`)} - ${duration}ms\n`));
69
70
  process.stdout.write(chalk_1.default.greenBright(`\n`));
70
71
  }
71
72
  });
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.envVarsInjectionMiddleware = envVarsInjectionMiddleware;
13
+ function envVarsInjectionMiddleware() {
14
+ return (ctx, next) => __awaiter(this, void 0, void 0, function* () {
15
+ var _a, _b, _c, _d, _e, _f, _g, _h;
16
+ const envVarsHeader = (_b = (_a = ctx.request) === null || _a === void 0 ? void 0 : _a.headers) === null || _b === void 0 ? void 0 : _b["x-ide-env-vars"];
17
+ if (envVarsHeader && typeof envVarsHeader === "string") {
18
+ try {
19
+ const envVars = JSON.parse(envVarsHeader);
20
+ if (envVars && typeof envVars === "object") {
21
+ for (const [key, value] of Object.entries(envVars)) {
22
+ if (key && value !== undefined && value !== null) {
23
+ process.env[String(key)] = String(value);
24
+ }
25
+ }
26
+ }
27
+ }
28
+ catch (e) {
29
+ console.warn("[envVarsInjection] Failed to parse env vars from header:", e);
30
+ }
31
+ }
32
+ const systemHeadersMapping = {
33
+ "xhs-cloud-env-id": "XHS_CLOUD_ENV_ID",
34
+ "xhs-cloud-function-id": "XHS_CLOUD_FUNCTION_ID",
35
+ "xhs-app-id": "XHS_APP_ID",
36
+ };
37
+ for (const [headerKey, envKey] of Object.entries(systemHeadersMapping)) {
38
+ const headerValue = (_d = (_c = ctx.request) === null || _c === void 0 ? void 0 : _c.headers) === null || _d === void 0 ? void 0 : _d[headerKey];
39
+ if (headerValue && typeof headerValue === "string") {
40
+ process.env[envKey] = headerValue;
41
+ }
42
+ }
43
+ const versionMapHeader = (_f = (_e = ctx.request) === null || _e === void 0 ? void 0 : _e.headers) === null || _f === void 0 ? void 0 : _f["xhs-cloud-env-version-map"];
44
+ const aliyunVersionHeader = (_h = (_g = ctx.request) === null || _g === void 0 ? void 0 : _g.headers) === null || _h === void 0 ? void 0 : _h["x-fc-version-id"];
45
+ if (versionMapHeader &&
46
+ typeof versionMapHeader === "string" &&
47
+ aliyunVersionHeader &&
48
+ typeof aliyunVersionHeader === "string") {
49
+ try {
50
+ const parsed = JSON.parse(versionMapHeader);
51
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
52
+ const map = parsed;
53
+ const mapped = map[aliyunVersionHeader];
54
+ if (mapped !== undefined && mapped !== null) {
55
+ process.env.XHS_CLOUD_ENV_VERSION = String(mapped);
56
+ }
57
+ }
58
+ }
59
+ catch (e) {
60
+ console.warn("[envVarsInjection] Failed to parse xhs-cloud-env-version-map:", e);
61
+ }
62
+ }
63
+ yield next();
64
+ });
65
+ }
package/lib/router.js CHANGED
@@ -10,6 +10,9 @@ class Router {
10
10
  this.router = (0, radix3_1.createRouter)({
11
11
  strictTrailingSlash: false,
12
12
  });
13
+ if (!(config && Array.isArray(config.routes))) {
14
+ return;
15
+ }
13
16
  for (const route of config.routes) {
14
17
  if (route.path) {
15
18
  const p = route.path.endsWith("/") ? route.path : route.path + "/";
@@ -40,6 +43,9 @@ class Router {
40
43
  }
41
44
  exports.Router = Router;
42
45
  function routeFunction(router, path) {
46
+ if (!router) {
47
+ return {};
48
+ }
43
49
  const matchedRoute = router.findRoute(path);
44
50
  if (matchedRoute) {
45
51
  return {
package/lib/server.js CHANGED
@@ -37,6 +37,7 @@ function createServer(options, router, projectConfig) {
37
37
  app.use(corsMiddleware());
38
38
  app.use((0, middlewares_1.logsQueryMiddleware)());
39
39
  app.use((0, middlewares_1.contextInjectionMiddleware)(options, projectConfig));
40
+ app.use((0, middlewares_1.envVarsInjectionMiddleware)());
40
41
  app.use((0, middlewares_1.eventIdMiddleware)());
41
42
  app.use((0, middlewares_1.apmMiddleware)());
42
43
  app.use((0, middlewares_1.asyncContextMiddleware)());
@@ -6,8 +6,10 @@ const secretKey = process.env.LANGFUSE_SECRET_KEY;
6
6
  const publicKey = process.env.LANGFUSE_PUBLIC_KEY;
7
7
  const baseUrl = process.env.LANGFUSE_BASEURL;
8
8
  exports.langfuse = new langfuse_1.Langfuse({
9
- secretKey: "sk-lf-f389bb1e-24db-4b42-b506-8d6118f45044",
10
- publicKey: "pk-lf-ee301553-7a64-4f0f-bf6b-a2ac1e5ca2df",
11
- baseUrl: "https://xray-langfuse.devops.xiaohongshu.com",
9
+ secretKey,
10
+ publicKey,
11
+ baseUrl,
12
+ environment: process.env.OPEN_PLATFORM_STAGE || "development",
13
+ enabled: Boolean(secretKey && publicKey && baseUrl),
12
14
  });
13
15
  exports.default = exports.langfuse;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.defaultConfig = exports.APM_MEASUREMENT_NAME = void 0;
4
+ const endpoints_1 = require("@vectorx/endpoints");
4
5
  exports.APM_MEASUREMENT_NAME = "vectorx_kit_agent_runtime";
5
6
  exports.defaultConfig = {
6
- endpoint: "https://apm-fe.xiaohongshu.com/api/data",
7
+ endpoint: (0, endpoints_1.resolveUrl)("@vectorx/functions-framework", "apmDataEndpoint"),
7
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectorx/functions-framework",
3
- "version": "0.8.0",
3
+ "version": "1.0.0",
4
4
  "description": "VectorX Functions Framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "types/index.d.ts",
@@ -25,7 +25,8 @@
25
25
  "node": ">=18.0.0"
26
26
  },
27
27
  "dependencies": {
28
- "@vectorx/ai-types": "0.7.0",
28
+ "@vectorx/ai-types": "1.0.0",
29
+ "@vectorx/endpoints": "1.0.0",
29
30
  "async_hooks": "^1.0.0",
30
31
  "chalk": "4",
31
32
  "commander": "^12.1.0",
@@ -72,5 +73,5 @@
72
73
  "lint": "eslint src --ext .ts",
73
74
  "run:demo": "node ./bin/rcb-ff.js"
74
75
  },
75
- "readme": "# VectorX Functions Framework\n\nVectorX Functions Framework 是一个用于构建和运行云函数的框架,提供了完整的日志系统支持。\n\n## 日志系统\n\n框架内置了完整的日志系统,支持请求日志记录和用户代码日志记录。\n\n### 配置\n\n在初始化框架时,可以配置日志系统:\n\n```typescript\nimport { createAgentServerFramework } from '@vectorx/functions-framework';\n\nconst framework = createAgentServerFramework({\n port: 3000,\n logging: {\n dirname: '/path/to/logs', // 日志存储目录\n maxSize: '20m', // 单个日志文件最大大小\n maxFiles: 14 // 保留的日志文件数量\n }\n});\n```\n\n### 日志类型\n\n框架支持两种类型的日志:\n\n- `ACCESS`: 访问日志,记录所有 HTTP 请求\n- `USERCODE`: 用户代码日志,记录用户函数中的日志\n\n### 日志查询 API\n\n框架提供了日志查询 API,用于查看和调试日志:\n\n```\nGET /@logs\n```\n\n#### 查询参数\n\n| 参数名 | 类型 | 必填 | 说明 |\n|--------|------|------|------|\n| type | string | 否 | 日志类型,可选值:ACCESS/USERCODE,默认 ACCESS |\n| limit | number | 否 | 返回的日志条数限制,默认 100 |\n| eventId | string | 否 | 按 eventId 过滤日志 |\n\n#### 示例\n\n1. 查询所有访问日志:\n```bash\ncurl 'http://localhost:3000/@logs?type=ACCESS'\n```\n\n2. 查询特定 eventId 的日志:\n```bash\ncurl 'http://localhost:3000/@logs?eventId=550e8400-e29b-41d4-a716-446655440000'\n```\n\n3. 限制返回条数:\n```bash\ncurl 'http://localhost:3000/@logs?limit=10'\n```\n\n4. 组合查询:\n```bash\ncurl 'http://localhost:3000/@logs?type=USERCODE&eventId=550e8400-e29b-41d4-a716-446655440000&limit=20'\n```\n\n#### 响应格式\n\n成功响应:\n```json\n{\n \"success\": true,\n \"data\": [\n {\n \"@timestamp\": \"2024-03-21T10:30:00.000Z\",\n \"level\": \"info\",\n \"eventId\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"message\": \"Request started\",\n \"method\": \"GET\",\n \"url\": \"/api/example\"\n }\n ]\n}\n```\n\n错误响应:\n```json\n{\n \"success\": false,\n \"error\": \"错误信息\"\n}\n```\n\n### 在代码中使用日志\n\n```typescript\nimport { functionsLogger } from '@vectorx/functions-framework';\nimport { LogLevel } from '@vectorx/functions-framework';\n\n// 记录访问日志\nfunctionsLogger.logAccesslog(LogLevel.INFO, 'Custom message', {\n customField: 'value'\n});\n\n// 记录用户代码日志\nfunctionsLogger.logUserCodelog([\n { message: 'User log message', level: 'info' }\n]);\n```\n\n### 日志格式\n\n每条日志都包含以下字段:\n\n- `@timestamp`: 日志时间戳\n- `level`: 日志级别\n- `eventId`: 请求 ID\n- `message`: 日志消息\n- 其他自定义字段\n\n## 开发\n\n### 运行测试\n\n```bash\nnpm test\n```\n\n### 构建\n\n```bash\nnpm run build\n``` \n\n\n### 环境管理\n读取 .env 文件,校验 env 文件的可用性,获取当前运行环境\n"
76
+ "readme": "# VectorX Functions Framework\n\nVectorX Functions Framework 是一个用于构建和运行云函数的框架,提供了完整的日志系统支持。\n\n## 日志系统\n\n框架内置了完整的日志系统,支持请求日志记录和用户代码日志记录。\n\n### 配置\n\n在初始化框架时,可以配置日志系统:\n\n```typescript\nimport { createAgentServerFramework } from '@vectorx/functions-framework';\n\nconst framework = createAgentServerFramework({\n port: 3000,\n logging: {\n dirname: '/path/to/logs', // 日志存储目录\n maxSize: '20m', // 单个日志文件最大大小\n maxFiles: 14 // 保留的日志文件数量\n }\n});\n```\n\n### 日志类型\n\n框架支持两种类型的日志:\n\n- `ACCESS`: 访问日志,记录所有 HTTP 请求\n- `USERCODE`: 用户代码日志,记录用户函数中的日志\n\n### 日志查询 API\n\n框架提供了日志查询 API,用于查看和调试日志:\n\n```\nGET /@logs\n```\n\n#### 查询参数\n\n| 参数名 | 类型 | 必填 | 说明 |\n|--------|------|------|------|\n| type | string | 否 | 日志类型,可选值:ACCESS/USERCODE,默认 ACCESS |\n| limit | number | 否 | 返回的日志条数限制,默认 100 |\n| eventId | string | 否 | 按 eventId 过滤日志 |\n\n#### 示例\n\n1. 查询所有访问日志:\n```bash\ncurl 'http://localhost:3000/@logs?type=ACCESS'\n```\n\n2. 查询特定 eventId 的日志:\n```bash\ncurl 'http://localhost:3000/@logs?eventId=550e8400-e29b-41d4-a716-446655440000'\n```\n\n3. 限制返回条数:\n```bash\ncurl 'http://localhost:3000/@logs?limit=10'\n```\n\n4. 组合查询:\n```bash\ncurl 'http://localhost:3000/@logs?type=USERCODE&eventId=550e8400-e29b-41d4-a716-446655440000&limit=20'\n```\n\n#### 响应格式\n\n成功响应:\n```json\n{\n \"success\": true,\n \"data\": [\n {\n \"@timestamp\": \"2024-03-21T10:30:00.000Z\",\n \"level\": \"info\",\n \"eventId\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"message\": \"Request started\",\n \"method\": \"GET\",\n \"url\": \"/api/example\"\n }\n ]\n}\n```\n\n错误响应:\n```json\n{\n \"success\": false,\n \"error\": \"错误信息\"\n}\n```\n\n### 在代码中使用日志\n\n```typescript\nimport { functionsLogger } from '@vectorx/functions-framework';\nimport { LogLevel } from '@vectorx/functions-framework';\n\n// 记录访问日志\nfunctionsLogger.logAccesslog(LogLevel.INFO, 'Custom message', {\n customField: 'value'\n});\n\n// 记录用户代码日志\nfunctionsLogger.logUserCodelog([\n { message: 'User log message', level: 'info' }\n]);\n```\n\n### 日志格式\n\n每条日志都包含以下字段:\n\n- `@timestamp`: 日志时间戳\n- `level`: 日志级别\n- `eventId`: 请求 ID\n- `message`: 日志消息\n- 其他自定义字段\n\n## 开发\n\n### 运行测试\n\n```bash\nnpm test\n```\n\n### 构建\n\n```bash\nnpm run build\n``` \n\n### 环境管理\n读取 .env 文件,校验 env 文件的可用性,获取当前运行环境\n\n\n## rcb-ff 自动检测项目类型\n\n当直接使用 `rcb-ff` 命令启动服务时,如果未指定 `--mode` 参数,会自动检测项目类型:\n\n### 检测规则\n\n1. **Agent 项目检测**:\n - 如果存在 `project.config.json` 文件且包含 `agentId` 字段,则判定为 `agent` 项目\n\n2. **Fun 项目检测**:\n - 如果不存在 `project.config.json` 或 `project.config.json` 中不存在 `agentId` 字段\n - 且存在 `agent-cloudbase-functions.json` 配置文件\n - 则判定为 `fun` 项目\n\n3. **默认行为**:\n - 如果无法确定项目类型,默认使用 `agent` 模式(保持向后兼容)\n\n### 使用示例\n\n```bash\n# 自动检测项目类型(推荐)\nrcb-ff --directory ./my-project\n\n# 明确指定模式(覆盖自动检测)\nrcb-ff --directory ./my-project --mode fun\nrcb-ff --directory ./my-project --mode agent\n```\n"
76
77
  }
@@ -1,4 +1,5 @@
1
1
  declare function getDependencyVersion(packageName: string, searchPath: string): string | null;
2
+ export type FrameworkMode = "agent" | "fun";
2
3
  export interface FrameworkOptions {
3
4
  port: number;
4
5
  timeoutMS: number;
@@ -18,6 +19,7 @@ export interface FrameworkOptions {
18
19
  redLangfuseConfig?: {
19
20
  enable?: boolean;
20
21
  };
22
+ mode?: FrameworkMode;
21
23
  }
22
24
  export type FunctionName = string;
23
25
  declare class AgentServerFramework {
@@ -27,6 +29,9 @@ declare class AgentServerFramework {
27
29
  constructor(options: FrameworkOptions);
28
30
  startServer(): Promise<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>>;
29
31
  getRegisteredFunction(fnName: string): Function;
32
+ private loadAgentModeConfigs;
33
+ private loadFunModeConfigs;
34
+ private loadAndRegisterFunctions;
30
35
  }
31
36
  export { getDependencyVersion };
32
37
  export declare function createAgentServerFramework(options: FrameworkOptions): AgentServerFramework;
@@ -1,5 +1,6 @@
1
1
  export { asyncContextMiddleware } from "./middle-async-context";
2
2
  export { contextInjectionMiddleware } from "./middle-context-injection";
3
+ export { envVarsInjectionMiddleware } from "./middle-env-vars-injection";
3
4
  export { eventIdMiddleware } from "./middle-event-id";
4
5
  export { functionRouteMiddleware } from "./middle-function-route";
5
6
  export { loggerMiddleware } from "./middle-common-logger";
@@ -0,0 +1,2 @@
1
+ import type { Context, Next } from "koa";
2
+ export declare function envVarsInjectionMiddleware(): (ctx: Context, next: Next) => Promise<void>;
package/types/router.d.ts CHANGED
@@ -2,14 +2,14 @@ import type { CloudFunctionConfig, RouteConfig } from "./function-loader";
2
2
  export declare class Router {
3
3
  private routes;
4
4
  private router;
5
- constructor(config: CloudFunctionConfig);
5
+ constructor(config?: CloudFunctionConfig);
6
6
  private addRoute;
7
7
  findRoute(path: string): RouteConfig | null;
8
8
  }
9
9
  export declare function routeFunction(router: Router | undefined, path: string): {
10
- routeDefinition: RouteConfig;
11
- handleFunction: Function;
12
- } | {
13
10
  routeDefinition?: undefined;
14
11
  handleFunction?: undefined;
12
+ } | {
13
+ routeDefinition: RouteConfig;
14
+ handleFunction: Function;
15
15
  };
package/types/server.d.ts CHANGED
@@ -24,10 +24,22 @@ export interface RcbContext {
24
24
  eventID: string;
25
25
  eventType: string;
26
26
  timestamp: number;
27
+ headers: Record<string, any>;
28
+ httpMethod: string;
29
+ path: string;
30
+ url: string;
31
+ query: Record<string, any>;
32
+ cookies: Record<string, string>;
33
+ trigger: "online" | "ide_debug" | "console_debug";
27
34
  httpContext: any;
28
35
  request: Request;
29
36
  logger: typeof functionsLogger;
30
37
  baseUrl: string;
38
+ set: (field: string, value: any) => void;
39
+ remove: (field: string) => void;
40
+ status: (code: number) => void;
41
+ setCookie: (name: string, value: string, options?: any) => void;
42
+ clearCookie: (name: string, options?: any) => void;
31
43
  sse: () => any;
32
44
  }
33
45
  export declare function createServer(options: FrameworkOptions, router: Router, projectConfig: ProjectConfig): http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;