@tacoreai/web-sdk 1.24.0 → 1.26.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
@@ -17,6 +17,45 @@ npm install @tacoreai/web-sdk
17
17
  ### AppsClient (Data & AI)
18
18
  示例略(保持原有章节)
19
19
 
20
+ ### TacoreActionSDK (Headless Capability)
21
+
22
+ 组织通用 Agent、外部 Agent 或 CLI 壳应通过 `TacoreActionSDK` 调用云应用暴露的 `/.tacore/capabilities.json`,而不是依赖应用 UI、DOM、按钮或页面路径。应用 GUI 只是 capability 的一个使用者,必须和 SDK 执行共享同一 Action Registry / service 层。
23
+
24
+ ```js
25
+ import { TacoreActionSDK } from '@tacoreai/web-sdk'
26
+
27
+ const actionSdk = new TacoreActionSDK({
28
+ accessToken: 'PLATFORM_ACCESS_TOKEN',
29
+ organizationId: 'org_xxx',
30
+ appId: 'crm_app_xxx',
31
+ })
32
+
33
+ const manifest = await actionSdk.listCapabilities()
34
+ const schema = await actionSdk.getCapabilitySchema('crm.lead.create')
35
+ const draft = await actionSdk.draft('crm.lead.create', {
36
+ name: 'Acme',
37
+ phone: '13800000000',
38
+ })
39
+
40
+ // 写入类能力应先展示 preview/dry-run,再经用户确认后执行。
41
+ await actionSdk.execute('crm.lead.create', {
42
+ draftId: draft.draftId,
43
+ action: {
44
+ name: 'crm.lead.create',
45
+ idempotencyKey: 'lead:13800000000',
46
+ steps: [
47
+ {
48
+ op: 'create',
49
+ modelName: 'lead',
50
+ data: { name: 'Acme', phone: '13800000000' },
51
+ },
52
+ ],
53
+ },
54
+ })
55
+ ```
56
+
57
+ SDK 封装平台 `/plat/agent/capabilities` 的发现、dry-run 与 execute 请求;核心执行仍落到 manifest 声明的 `actions/run` 事务契约,CLI 只是同一接口的一层命令行包装。
58
+
20
59
  ### AppsClient (Events)
21
60
 
22
61
  应用自定义埋点应使用事件日志接口,不要把高频事件写入通用数据模型。
@@ -0,0 +1,206 @@
1
+ import { getAppsApiBaseUrl, getGlobalOptions, isBrowser } from "./utils/index.js";
2
+
3
+ const PLATFORM_ACCESS_TOKEN_STORAGE_KEY = "tacoreai_access_token";
4
+
5
+ function normalizeString(value) {
6
+ return String(value || "").trim();
7
+ }
8
+
9
+ function normalizeAccessToken(value) {
10
+ const token = normalizeString(value);
11
+ return token.replace(/^Bearer\s+/i, "").trim();
12
+ }
13
+
14
+ function readPlatformAccessToken() {
15
+ if (!isBrowser) {
16
+ return "";
17
+ }
18
+ try {
19
+ return normalizeAccessToken(localStorage.getItem(PLATFORM_ACCESS_TOKEN_STORAGE_KEY));
20
+ } catch (error) {
21
+ console.error("[TacoreActionSDK] Failed to read platform access token:", error);
22
+ return "";
23
+ }
24
+ }
25
+
26
+ function buildQuery(params = {}) {
27
+ const searchParams = new URLSearchParams();
28
+ Object.entries(params).forEach(([key, value]) => {
29
+ if (value === undefined || value === null || value === "") {
30
+ return;
31
+ }
32
+ searchParams.append(key, typeof value === "object" ? JSON.stringify(value) : String(value));
33
+ });
34
+ const query = searchParams.toString();
35
+ return query ? `?${query}` : "";
36
+ }
37
+
38
+ function resolveDraftCapabilityId(draft) {
39
+ return normalizeString(draft?.capabilityId || draft?.capability?.capabilityId);
40
+ }
41
+
42
+ export class TacoreActionSDK {
43
+ constructor(config = {}) {
44
+ const globalOptions = getGlobalOptions();
45
+ this.config = {
46
+ ...globalOptions.clientDefaultConfig,
47
+ apiBaseUrl: getAppsApiBaseUrl(),
48
+ organizationId: "",
49
+ projectId: "",
50
+ appId: "",
51
+ ...config,
52
+ };
53
+ this.accessToken = normalizeAccessToken(config.accessToken || "");
54
+ }
55
+
56
+ updateConfig(nextConfig = {}) {
57
+ if (Object.prototype.hasOwnProperty.call(nextConfig, "accessToken")) {
58
+ this.setAccessToken(nextConfig.accessToken);
59
+ }
60
+ this.config = {
61
+ ...this.config,
62
+ ...nextConfig,
63
+ accessToken: this.accessToken,
64
+ };
65
+ }
66
+
67
+ setAccessToken(token) {
68
+ this.accessToken = normalizeAccessToken(token);
69
+ this.config.accessToken = this.accessToken;
70
+ return this.accessToken;
71
+ }
72
+
73
+ getAccessToken() {
74
+ return this.accessToken || readPlatformAccessToken();
75
+ }
76
+
77
+ getHeaders(options = {}) {
78
+ const headers = {
79
+ "Content-Type": "application/json",
80
+ ...(options.headers || {}),
81
+ };
82
+ const organizationId = normalizeString(options.organizationId || this.config.organizationId);
83
+ const projectId = normalizeString(options.projectId || this.config.projectId);
84
+ const appId = normalizeString(options.appId || this.config.appId);
85
+ const token = this.getAccessToken();
86
+
87
+ if (organizationId) {
88
+ headers["x-org-id"] = organizationId;
89
+ }
90
+ if (projectId) {
91
+ headers["x-project-id"] = projectId;
92
+ }
93
+ if (appId) {
94
+ headers["x-app-id"] = appId;
95
+ }
96
+ if (token) {
97
+ headers.Authorization = `Bearer ${token}`;
98
+ }
99
+ return headers;
100
+ }
101
+
102
+ async request(endpoint, options = {}) {
103
+ const response = await fetch(`${this.config.apiBaseUrl}${endpoint}`, {
104
+ ...options,
105
+ headers: this.getHeaders(options),
106
+ });
107
+ const result = await response.json().catch(() => ({}));
108
+ if (!response.ok || result?.success === false) {
109
+ const error = new Error(result?.message || result?.error || `HTTP ${response.status}: ${response.statusText}`);
110
+ error.status = response.status;
111
+ error.code = result?.code;
112
+ error.details = result?.details;
113
+ error.responseData = result;
114
+ throw error;
115
+ }
116
+ return result?.data ?? result;
117
+ }
118
+
119
+ get(endpoint, params = {}, options = {}) {
120
+ return this.request(`${endpoint}${buildQuery(params)}`, {
121
+ ...options,
122
+ method: "GET",
123
+ });
124
+ }
125
+
126
+ post(endpoint, body = {}, options = {}) {
127
+ return this.request(endpoint, {
128
+ ...options,
129
+ method: "POST",
130
+ body: JSON.stringify(body),
131
+ });
132
+ }
133
+
134
+ listCapabilities(options = {}) {
135
+ const appId = normalizeString(options.appId || this.config.appId);
136
+ return this.get("/plat/agent/capabilities", { appId }, { appId, ...options });
137
+ }
138
+
139
+ getCapabilitySchema(capabilityId, options = {}) {
140
+ const appId = normalizeString(options.appId || this.config.appId);
141
+ const normalizedCapabilityId = encodeURIComponent(normalizeString(capabilityId));
142
+ return this.get(`/plat/agent/capabilities/${normalizedCapabilityId}`, { appId }, { appId, ...options });
143
+ }
144
+
145
+ dryRun(capabilityId, options = {}) {
146
+ const appId = normalizeString(options.appId || this.config.appId);
147
+ return this.post("/plat/agent/capabilities/dry-run", {
148
+ appId,
149
+ capabilityId: normalizeString(capabilityId),
150
+ input: options.input || {},
151
+ action: options.action || null,
152
+ options: options.options || {},
153
+ }, { appId, ...options });
154
+ }
155
+
156
+ draft(capabilityId, inputOrOptions = {}, maybeOptions = {}) {
157
+ const isOptionsObject = inputOrOptions && typeof inputOrOptions === "object" && (
158
+ Object.prototype.hasOwnProperty.call(inputOrOptions, "input") ||
159
+ Object.prototype.hasOwnProperty.call(inputOrOptions, "appId") ||
160
+ Object.prototype.hasOwnProperty.call(inputOrOptions, "options")
161
+ );
162
+ const options = isOptionsObject
163
+ ? inputOrOptions
164
+ : {
165
+ ...maybeOptions,
166
+ input: inputOrOptions || {},
167
+ };
168
+ return this.dryRun(capabilityId, options);
169
+ }
170
+
171
+ preview(draftOrCapabilityId, options = {}) {
172
+ if (draftOrCapabilityId && typeof draftOrCapabilityId === "object") {
173
+ if (draftOrCapabilityId.dryRun === true) {
174
+ return Promise.resolve(draftOrCapabilityId);
175
+ }
176
+ return this.dryRun(resolveDraftCapabilityId(draftOrCapabilityId), {
177
+ ...draftOrCapabilityId,
178
+ ...options,
179
+ });
180
+ }
181
+ return this.dryRun(draftOrCapabilityId, options);
182
+ }
183
+
184
+ execute(draftOrCapabilityId, options = {}) {
185
+ const draft = draftOrCapabilityId && typeof draftOrCapabilityId === "object" ? draftOrCapabilityId : null;
186
+ const capabilityId = draft ? resolveDraftCapabilityId(draft) : normalizeString(draftOrCapabilityId);
187
+ const appId = normalizeString(options.appId || draft?.appId || this.config.appId);
188
+ return this.post("/plat/agent/capabilities/execute", {
189
+ appId,
190
+ capabilityId,
191
+ draftId: normalizeString(options.draftId || draft?.draftId),
192
+ input: options.input || draft?.input || {},
193
+ action: options.action || draft?.action || null,
194
+ options: options.options || {},
195
+ }, { appId, ...options });
196
+ }
197
+
198
+ runTransactionAction({ appId, capabilityId, input = {}, action, options = {} } = {}) {
199
+ return this.execute(capabilityId, {
200
+ appId,
201
+ input,
202
+ action,
203
+ options,
204
+ });
205
+ }
206
+ }
@@ -1,4 +1,5 @@
1
1
  import { AppsClient } from "./AppsClient.js";
2
+ import { AppsAuthManager } from "./AppsAuthManager.js";
2
3
 
3
4
  export const APPSERVER_REQUEST_DATA_FIELD = "requestData";
4
5
 
@@ -30,6 +31,43 @@ const getRequestSource = (isPlatformProxyRequest, isSchedulerRequest) => {
30
31
  return isPreviewMode() ? "preview-direct" : "external-direct";
31
32
  };
32
33
 
34
+ const getAppServerServiceToken = (env) => (
35
+ env.tacoreAppServerServiceToken ||
36
+ process.env.TACORE_APP_SERVER_SERVICE_TOKEN ||
37
+ null
38
+ );
39
+
40
+ const getTacoreServerInteropAppServerApiKey = (env) => (
41
+ env.tacoreServerInteropAppServerApiKey ||
42
+ process.env.TACORE_SERVER_INTEROP_APP_SERVER_API_KEY ||
43
+ null
44
+ );
45
+
46
+ const hasAppServerServiceAuthCredential = (env) => (
47
+ Boolean(getTacoreServerInteropAppServerApiKey(env)) &&
48
+ Boolean(getAppServerServiceToken(env))
49
+ );
50
+
51
+ const shouldUseAppServerMachineAuth = ({
52
+ env,
53
+ isPlatformProxyRequest,
54
+ isSchedulerRequest,
55
+ isExternalApiKeyRequest,
56
+ accessToken,
57
+ appServerApiKey,
58
+ previewAppServerApiKey,
59
+ }) => {
60
+ if (isExternalApiKeyRequest && accessToken) {
61
+ return false;
62
+ }
63
+
64
+ if (isPlatformProxyRequest || isSchedulerRequest) {
65
+ return hasAppServerServiceAuthCredential(env);
66
+ }
67
+
68
+ return Boolean(appServerApiKey) || Boolean(previewAppServerApiKey);
69
+ };
70
+
33
71
  const createRequestScopedAppsClient = (env, options = {}) => {
34
72
  const {
35
73
  accessToken = null,
@@ -63,6 +101,35 @@ const createRequestScopedAppsClient = (env, options = {}) => {
63
101
  return new AppsClient(clientAppId, config);
64
102
  };
65
103
 
104
+ const resolveCallerUser = async (env, accessToken) => {
105
+ if (!accessToken) {
106
+ return null;
107
+ }
108
+
109
+ try {
110
+ const authManager = new AppsAuthManager(env.dataSourceId || env.appId, {
111
+ ...env,
112
+ accessToken,
113
+ });
114
+ return await authManager.getCurrentUser();
115
+ } catch (error) {
116
+ console.warn("[AppServer] Failed to resolve caller user:", error?.message || error);
117
+ return null;
118
+ }
119
+ };
120
+
121
+ const createCallerAppsClient = (env, accessToken) => {
122
+ if (!accessToken) {
123
+ return null;
124
+ }
125
+
126
+ return createRequestScopedAppsClient(env, {
127
+ accessToken,
128
+ clientAppId: env.dataSourceId || env.appId,
129
+ enableAppServerServiceAuth: false,
130
+ });
131
+ };
132
+
66
133
  const isPreviewApiKeyShapeValid = (apiKey) => {
67
134
  if (typeof apiKey !== "string") return false;
68
135
  const value = apiKey.trim();
@@ -360,12 +427,23 @@ export const createAppServerRuntime = async (options = {}) => {
360
427
  willForwardPreviewAppServerApiKey: Boolean(previewAppServerApiKey),
361
428
  });
362
429
 
430
+ const useAppServerMachineAuth = shouldUseAppServerMachineAuth({
431
+ env,
432
+ isPlatformProxyRequest,
433
+ isSchedulerRequest,
434
+ isExternalApiKeyRequest,
435
+ accessToken: requestAccessToken,
436
+ appServerApiKey,
437
+ previewAppServerApiKey,
438
+ });
363
439
  const appsClient = createRequestScopedAppsClient(env, {
364
- accessToken: requestAccessToken || null,
365
- appServerApiKey: requestAccessToken ? null : appServerApiKey,
440
+ accessToken: useAppServerMachineAuth ? null : requestAccessToken || null,
441
+ appServerApiKey: useAppServerMachineAuth ? appServerApiKey : requestAccessToken ? null : appServerApiKey,
366
442
  previewAppServerApiKey,
367
- enableAppServerServiceAuth: Boolean((isSchedulerRequest || isExternalApiKeyRequest) && !requestAccessToken),
443
+ enableAppServerServiceAuth: useAppServerMachineAuth,
368
444
  });
445
+ const callerUser = await resolveCallerUser(env, requestAccessToken);
446
+ const callerAppsClient = createCallerAppsClient(env, requestAccessToken);
369
447
  console.log(`[AppServer Debug] scoped AppsClient api="${apiName}"`, {
370
448
  hasAccessToken: Boolean(appsClient.getAccessToken()),
371
449
  hasAppServerApiKey: Boolean(appsClient.config.appServerApiKey),
@@ -373,13 +451,19 @@ export const createAppServerRuntime = async (options = {}) => {
373
451
  hasInteropKey: Boolean(appsClient.config.tacoreServerInteropAppServerApiKey),
374
452
  serviceAuthEnabled: appsClient.config.enableAppServerServiceAuth === true,
375
453
  hasServiceToken: Boolean(appsClient.config.tacoreAppServerServiceToken),
454
+ usesAppServerMachineAuth: useAppServerMachineAuth,
455
+ hasCallerUser: Boolean(callerUser?.id),
456
+ hasCallerAppsClient: Boolean(callerAppsClient),
376
457
  });
377
458
  const data = await appsClient.invokeAppServerAPI(apiName, payload, {
378
459
  appsClient,
460
+ callerAppsClient,
379
461
  req,
380
462
  res,
381
463
  files: normalizeUploadedFiles(req.files || []),
382
464
  requestSource,
465
+ callerAccessToken: requestAccessToken || null,
466
+ callerUser,
383
467
  });
384
468
 
385
469
  if (data && typeof data === "object" && !Buffer.isBuffer(data)) {
@@ -7,22 +7,6 @@ export const appsClientIamMethods = {
7
7
  return this._post("/apps/iam/bootstrap", params);
8
8
  },
9
9
 
10
- async listIamOrgUnits(params = {}) {
11
- return this._post("/apps/iam/org-units/list", params);
12
- },
13
-
14
- async createIamOrgUnit(params = {}) {
15
- return this._post("/apps/iam/org-units", params);
16
- },
17
-
18
- async updateIamOrgUnit(params = {}) {
19
- return this._post("/apps/iam/org-units/update", params);
20
- },
21
-
22
- async deleteIamOrgUnit(params = {}) {
23
- return this._post("/apps/iam/org-units/delete", params);
24
- },
25
-
26
10
  async listIamRoles(params = {}) {
27
11
  return this._post("/apps/iam/roles/list", params);
28
12
  },
@@ -39,22 +23,6 @@ export const appsClientIamMethods = {
39
23
  return this._post("/apps/iam/roles/delete", params);
40
24
  },
41
25
 
42
- async listIamStaff(params = {}) {
43
- return this._post("/apps/iam/staff/list", params);
44
- },
45
-
46
- async createIamStaff(params = {}) {
47
- return this._post("/apps/iam/staff", params);
48
- },
49
-
50
- async updateIamStaff(params = {}) {
51
- return this._post("/apps/iam/staff/update", params);
52
- },
53
-
54
- async deleteIamStaff(params = {}) {
55
- return this._post("/apps/iam/staff/delete", params);
56
- },
57
-
58
26
  async listModelPolicies(params = {}) {
59
27
  return this._post("/apps/data/model-policies/list", params);
60
28
  },
@@ -56,6 +56,7 @@ class AppsClient extends BaseAppsClient {
56
56
  /**
57
57
  * 定义策略适配器映射
58
58
  * 将 invoke 的对象参数转换为老方法的位置参数
59
+ * stone手动: 注意!!! 无需前端特殊逻辑的新方法,仅需直接在后端新增 xxx/yyy 这种接口即可,websdk 不需要发版,应用层直接 client.invoke('xxx/yyy', params) 即可调用;需要前端特殊逻辑适配的方法,才需要在这里新增适配器
59
60
  */
60
61
  get adapters() {
61
62
  return {
@@ -113,18 +114,10 @@ class AppsClient extends BaseAppsClient {
113
114
  // === 企业 IAM / 审计 / 文件鉴权 ===
114
115
  "iam/me": params => this.getIamContext(params),
115
116
  "iam/bootstrap": params => this.bootstrapIam(params),
116
- "iam/org-units/list": params => this.listIamOrgUnits(params),
117
- "iam/org-units": params => this.createIamOrgUnit(params),
118
- "iam/org-units/update": params => this.updateIamOrgUnit(params),
119
- "iam/org-units/delete": params => this.deleteIamOrgUnit(params),
120
117
  "iam/roles/list": params => this.listIamRoles(params),
121
118
  "iam/roles": params => this.createIamRole(params),
122
119
  "iam/roles/update": params => this.updateIamRole(params),
123
120
  "iam/roles/delete": params => this.deleteIamRole(params),
124
- "iam/staff/list": params => this.listIamStaff(params),
125
- "iam/staff": params => this.createIamStaff(params),
126
- "iam/staff/update": params => this.updateIamStaff(params),
127
- "iam/staff/delete": params => this.deleteIamStaff(params),
128
121
  "data/model-policies/list": params => this.listModelPolicies(params),
129
122
  "data/model-policies/declare": params => this.declareModelPolicy(params),
130
123
  "audit/write": params => this.writeAuditLog(params),
package/index.js CHANGED
@@ -4,6 +4,7 @@ import * as utils from "./utils/index.js"
4
4
  export { AppsClient } from "./database/core/apps/AppsClient.js"
5
5
  export { AppsAuthManager } from "./database/core/apps/AppsAuthManager.js"
6
6
  export { AppsPublicClient } from "./database/core/apps/AppsPublicClient.js"
7
+ export { TacoreActionSDK } from "./TacoreActionSDK.js"
7
8
 
8
9
  // 导出工具函数
9
10
  export { utils }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tacoreai/web-sdk",
3
3
  "description": "This file is for app server package, not the real npm package",
4
- "version": "1.24.0",
4
+ "version": "1.26.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public",