@super_studio/ecforce-ai-agent-server 1.4.0-canary.1 → 1.4.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
@@ -1,6 +1,6 @@
1
1
  # `@super_studio/ecforce-ai-agent-server`
2
2
 
3
- ecforce AI Agent のサーバー向け SDK です。
3
+ ecforce AIエージェント のサーバー向け SDK です。
4
4
 
5
5
  主に次の 2 つを提供します。
6
6
 
@@ -56,7 +56,7 @@ AI_AGENT_API_ENDPOINT=https://dev-agent.ec-force.com
56
56
 
57
57
  ### `spst()`
58
58
 
59
- Vercel AI SDK の `generateText()` / `streamText()` などから、ecforce AI Agent の proxy provider を使いたいときに使います。
59
+ Vercel AI SDK の `generateText()` / `streamText()` などから、ecforce AIエージェント の proxy provider を使いたいときに使います。
60
60
 
61
61
  用途:
62
62
 
@@ -119,6 +119,47 @@ type AgentSession = {
119
119
  };
120
120
  ```
121
121
 
122
+ ### 外部アプリで利用規約の同意を管理する
123
+
124
+ 利用規約は `https://agent.ec-force.com/terms` で公開されています。外部アプリでは、このページへのリンクを表示してから同意を記録してください。
125
+
126
+ DB に登録済みのユーザーは、Accounts API を呼ばずに同意状況だけ取得できます。
127
+
128
+ ```ts
129
+ const terms = await client.internalTerms.status({
130
+ email: "user@example.com",
131
+ projectId: "project_123",
132
+ });
133
+
134
+ if (!terms.accepted) {
135
+ // 利用規約へのリンクと同意チェックボックスを表示
136
+ }
137
+ ```
138
+
139
+ ユーザーが同意したら、次の API で記録します。未登録ユーザーも同時に作成する場合だけ `createIfMissing: true` を指定してください。
140
+
141
+ ```ts
142
+ await client.internalTerms.accept({
143
+ email: "user@example.com",
144
+ projectId: "project_123",
145
+ createIfMissing: true,
146
+ });
147
+ ```
148
+
149
+ ユーザー情報も必要な場合は `resolve` を使います。`createIfMissing` の既定値は `false` です。
150
+
151
+ ```ts
152
+ const user = await client.internalUser.resolve({
153
+ email: "user@example.com",
154
+ projectId: "project_123",
155
+ createIfMissing: true,
156
+ });
157
+
158
+ console.log(user.role, user.plan, user.termsAcceptedAt);
159
+ ```
160
+
161
+ 存在しないユーザーに `createIfMissing: false` でアクセスすると、`NOT_FOUND` エラーになります。ユーザー作成時は既存のルールに従い、有料プロジェクトでは Accounts に登録済みのメールアドレスだけ作成できます。
162
+
122
163
  ### 内部 usage summary を取得する
123
164
 
124
165
  内部 API の利用例として、`/v1/internal/usage/summary` に対応する usage summary 取得は次のように呼べます。
@@ -266,6 +307,114 @@ const result = streamText({
266
307
  });
267
308
  ```
268
309
 
310
+ ### エラーハンドリング
311
+
312
+ ユーザーが未登録、または利用規約に未同意の場合、プロキシは判別可能なエラーコードを返します。
313
+
314
+ | HTTP | `error.code` | 意味 |
315
+ | --- | --- | --- |
316
+ | 404 | `user_not_found` | 指定したプロジェクトにユーザーが存在しない |
317
+ | 403 | `terms_not_accepted` | ユーザーが利用規約に同意していない |
318
+
319
+ ```ts
320
+ import { APICallError } from "ai";
321
+
322
+ function isTermsNotAcceptedError(error: unknown) {
323
+ return (
324
+ APICallError.isInstance(error) &&
325
+ error.statusCode === 403 &&
326
+ error.responseBody?.includes("terms_not_accepted") === true
327
+ );
328
+ }
329
+ ```
330
+
331
+ クレジット上限に達すると、プロキシは **429** を返します。
332
+
333
+ ```json
334
+ {
335
+ "error": {
336
+ "message": "Credit limit exceeded.",
337
+ "type": "rate_limit_error",
338
+ "code": "credit_limit_exceeded"
339
+ }
340
+ }
341
+ ```
342
+
343
+ Vercel AI SDK では、このエラーは `APICallError` として扱われます。`generateText` と `streamText` で受け取り方が異なる点に注意してください。
344
+
345
+ #### `generateText`
346
+
347
+ `generateText()` 自体が throw します。
348
+
349
+ ```ts
350
+ import { APICallError, generateText } from "ai";
351
+ import { createSpstProvider } from "@super_studio/ecforce-ai-agent-server/provider";
352
+
353
+ const provider = createSpstProvider({ callerApp: "admin-dashboard" });
354
+
355
+ try {
356
+ await generateText({
357
+ model: provider("openai/gpt-5.4"),
358
+ prompt: "売上の改善案を3つ提案して",
359
+ maxRetries: 0,
360
+ providerOptions: {
361
+ spst: {
362
+ projectId: "project_123",
363
+ email: "user@example.com",
364
+ },
365
+ },
366
+ });
367
+ } catch (error) {
368
+ if (isCreditLimitError(error)) {
369
+ // 例: UI に「クレジット上限に達しました」を表示
370
+ return;
371
+ }
372
+
373
+ throw error;
374
+ }
375
+
376
+ function isCreditLimitError(error: unknown) {
377
+ return (
378
+ APICallError.isInstance(error) &&
379
+ error.statusCode === 429 &&
380
+ error.responseBody?.includes("credit_limit_exceeded") === true
381
+ );
382
+ }
383
+ ```
384
+
385
+ #### `streamText`
386
+
387
+ `streamText()` の HTTP エラーは `onError` で受け取ります。`await result.text` 側で credit limit を判定する必要はありません。
388
+
389
+ ```ts
390
+ import { APICallError, streamText } from "ai";
391
+ import { createSpstProvider } from "@super_studio/ecforce-ai-agent-server/provider";
392
+
393
+ const provider = createSpstProvider({ callerApp: "admin-dashboard" });
394
+
395
+ const result = streamText({
396
+ model: provider("openai/gpt-5.4-mini"),
397
+ prompt: "FAQ の草案を作成して",
398
+ maxRetries: 0,
399
+ providerOptions: {
400
+ spst: {
401
+ projectId: "project_123",
402
+ email: "user@example.com",
403
+ },
404
+ },
405
+ onError: ({ error }) => {
406
+ if (isCreditLimitError(error)) {
407
+ // 例: UI に「クレジット上限に達しました」を表示
408
+ return;
409
+ }
410
+
411
+ console.error(error);
412
+ },
413
+ });
414
+
415
+ return result.toUIMessageStreamResponse();
416
+ ```
417
+
269
418
  ### Google Search tool を併用する
270
419
 
271
420
  Google 系モデルでは、AI SDK 側のツールを組み合わせて使えます。
@@ -1,7 +1,7 @@
1
1
  //#region src/mcp-auth.d.ts
2
2
  /**
3
3
  * 共通のMCPトークンのペイロード。
4
- * すべてのapps(cdp, ma, bi)はこのトークンの形になります。
4
+ * すべてのapps(aidp, ma, bi)はこのトークンの形になります。
5
5
  */
6
6
  type MCPTokenPayload = {
7
7
  source: string;
@@ -1,7 +1,7 @@
1
1
  //#region src/mcp-auth.d.ts
2
2
  /**
3
3
  * 共通のMCPトークンのペイロード。
4
- * すべてのapps(cdp, ma, bi)はこのトークンの形になります。
4
+ * すべてのapps(aidp, ma, bi)はこのトークンの形になります。
5
5
  */
6
6
  type MCPTokenPayload = {
7
7
  source: string;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-auth.mjs","names":[],"sources":["../src/mcp-auth.ts"],"sourcesContent":["import { decode, encode } from \"./lib/jwt\";\n\n/**\n * 共通のMCPトークンのペイロード。\n * すべてのapps(cdp, ma, bi)はこのトークンの形になります。\n */\nexport type MCPTokenPayload = {\n source: string;\n user: {\n id: string;\n name: string;\n email: string;\n projectId: string;\n };\n};\n\nconst DEFAULT_MAX_AGE = 60 * 10; // 10 minutes\nconst DEFAULT_SALT = process.env.STAGE === \"local\" ? \"dev\" : process.env.STAGE;\n\n/**\n * Uses Auth.js JWT encoding/decoding.\n * @see https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/jwt.ts\n */\nexport async function createMCPToken(\n payload: MCPTokenPayload,\n options?: {\n secret?: string;\n salt?: string;\n maxAge?: number;\n },\n) {\n const maxAge = options?.maxAge ?? DEFAULT_MAX_AGE;\n const secret = options?.secret ?? process.env.MCP_TOKEN_SECRET;\n const salt = options?.salt ?? DEFAULT_SALT;\n if (!secret || !salt) {\n throw new Error(\"Secret or salt is not set\");\n }\n const token = await encode<MCPTokenPayload>({\n token: payload,\n secret,\n salt,\n maxAge,\n });\n return {\n token,\n expiresAt: new Date(Date.now() + maxAge * 1000),\n };\n}\n\n/**\n * Uses Auth.js JWT encoding/decoding.\n * @see https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/jwt.ts\n */\nexport async function decodeMCPToken(\n token: string,\n options?: {\n secret?: string;\n salt?: string;\n },\n): Promise<MCPTokenPayload> {\n const secret = options?.secret ?? process.env.MCP_TOKEN_SECRET;\n const salt = options?.salt ?? DEFAULT_SALT;\n if (!secret || !salt) {\n throw new Error(\"Secret or salt is not set\");\n }\n const decoded = await decode<MCPTokenPayload>({\n token,\n secret,\n salt,\n });\n if (!decoded) {\n throw new Error(\"Invalid token\");\n }\n return decoded;\n}\n\nexport function getMcpToken(req: Request) {\n const mcpToken = req.headers.get(\"X-MCP-Token\");\n if (!mcpToken || typeof mcpToken !== \"string\") {\n throw new Error(\"Unauthorized\");\n }\n return mcpToken;\n}\n"],"mappings":";;;AAgBA,MAAM,kBAAkB;AACxB,MAAM,eAAe,QAAQ,IAAI,UAAU,UAAU,QAAQ,QAAQ,IAAI;;;;;AAMzE,eAAsB,eACpB,SACA,SAKA;;CACA,MAAM,8EAAS,QAAS,mEAAU;CAClC,MAAM,8EAAS,QAAS,mEAAU,QAAQ,IAAI;CAC9C,MAAM,0EAAO,QAAS,6DAAQ;AAC9B,KAAI,CAAC,UAAU,CAAC,KACd,OAAM,IAAI,MAAM,4BAA4B;AAQ9C,QAAO;EACL,OAPY,MAAM,OAAwB;GAC1C,OAAO;GACP;GACA;GACA;GACD,CAAC;EAGA,WAAW,IAAI,KAAK,KAAK,KAAK,GAAG,SAAS,IAAK;EAChD;;;;;;AAOH,eAAsB,eACpB,OACA,SAI0B;;CAC1B,MAAM,+EAAS,QAAS,qEAAU,QAAQ,IAAI;CAC9C,MAAM,2EAAO,QAAS,+DAAQ;AAC9B,KAAI,CAAC,UAAU,CAAC,KACd,OAAM,IAAI,MAAM,4BAA4B;CAE9C,MAAM,UAAU,MAAM,OAAwB;EAC5C;EACA;EACA;EACD,CAAC;AACF,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,gBAAgB;AAElC,QAAO;;AAGT,SAAgB,YAAY,KAAc;CACxC,MAAM,WAAW,IAAI,QAAQ,IAAI,cAAc;AAC/C,KAAI,CAAC,YAAY,OAAO,aAAa,SACnC,OAAM,IAAI,MAAM,eAAe;AAEjC,QAAO"}
1
+ {"version":3,"file":"mcp-auth.mjs","names":[],"sources":["../src/mcp-auth.ts"],"sourcesContent":["import { decode, encode } from \"./lib/jwt\";\n\n/**\n * 共通のMCPトークンのペイロード。\n * すべてのapps(aidp, ma, bi)はこのトークンの形になります。\n */\nexport type MCPTokenPayload = {\n source: string;\n user: {\n id: string;\n name: string;\n email: string;\n projectId: string;\n };\n};\n\nconst DEFAULT_MAX_AGE = 60 * 10; // 10 minutes\nconst DEFAULT_SALT = process.env.STAGE === \"local\" ? \"dev\" : process.env.STAGE;\n\n/**\n * Uses Auth.js JWT encoding/decoding.\n * @see https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/jwt.ts\n */\nexport async function createMCPToken(\n payload: MCPTokenPayload,\n options?: {\n secret?: string;\n salt?: string;\n maxAge?: number;\n },\n) {\n const maxAge = options?.maxAge ?? DEFAULT_MAX_AGE;\n const secret = options?.secret ?? process.env.MCP_TOKEN_SECRET;\n const salt = options?.salt ?? DEFAULT_SALT;\n if (!secret || !salt) {\n throw new Error(\"Secret or salt is not set\");\n }\n const token = await encode<MCPTokenPayload>({\n token: payload,\n secret,\n salt,\n maxAge,\n });\n return {\n token,\n expiresAt: new Date(Date.now() + maxAge * 1000),\n };\n}\n\n/**\n * Uses Auth.js JWT encoding/decoding.\n * @see https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/jwt.ts\n */\nexport async function decodeMCPToken(\n token: string,\n options?: {\n secret?: string;\n salt?: string;\n },\n): Promise<MCPTokenPayload> {\n const secret = options?.secret ?? process.env.MCP_TOKEN_SECRET;\n const salt = options?.salt ?? DEFAULT_SALT;\n if (!secret || !salt) {\n throw new Error(\"Secret or salt is not set\");\n }\n const decoded = await decode<MCPTokenPayload>({\n token,\n secret,\n salt,\n });\n if (!decoded) {\n throw new Error(\"Invalid token\");\n }\n return decoded;\n}\n\nexport function getMcpToken(req: Request) {\n const mcpToken = req.headers.get(\"X-MCP-Token\");\n if (!mcpToken || typeof mcpToken !== \"string\") {\n throw new Error(\"Unauthorized\");\n }\n return mcpToken;\n}\n"],"mappings":";;;AAgBA,MAAM,kBAAkB;AACxB,MAAM,eAAe,QAAQ,IAAI,UAAU,UAAU,QAAQ,QAAQ,IAAI;;;;;AAMzE,eAAsB,eACpB,SACA,SAKA;;CACA,MAAM,8EAAS,QAAS,mEAAU;CAClC,MAAM,8EAAS,QAAS,mEAAU,QAAQ,IAAI;CAC9C,MAAM,0EAAO,QAAS,6DAAQ;AAC9B,KAAI,CAAC,UAAU,CAAC,KACd,OAAM,IAAI,MAAM,4BAA4B;AAQ9C,QAAO;EACL,OAPY,MAAM,OAAwB;GAC1C,OAAO;GACP;GACA;GACA;GACD,CAAC;EAGA,WAAW,IAAI,KAAK,KAAK,KAAK,GAAG,SAAS,IAAK;EAChD;;;;;;AAOH,eAAsB,eACpB,OACA,SAI0B;;CAC1B,MAAM,+EAAS,QAAS,qEAAU,QAAQ,IAAI;CAC9C,MAAM,2EAAO,QAAS,+DAAQ;AAC9B,KAAI,CAAC,UAAU,CAAC,KACd,OAAM,IAAI,MAAM,4BAA4B;CAE9C,MAAM,UAAU,MAAM,OAAwB;EAC5C;EACA;EACA;EACD,CAAC;AACF,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,gBAAgB;AAElC,QAAO;;AAGT,SAAgB,YAAY,KAAc;CACxC,MAAM,WAAW,IAAI,QAAQ,IAAI,cAAc;AAC/C,KAAI,CAAC,YAAY,OAAO,aAAa,SACnC,OAAM,IAAI,MAAM,eAAe;AAEjC,QAAO"}
@@ -262,13 +262,41 @@ var Api = class extends HttpClient {
262
262
  format: "json"
263
263
  }, params))
264
264
  };
265
- this.internalUser = { list2: (query, params = {}) => this.request(require_objectSpread2._objectSpread2({
266
- path: `/v1/internal/user`,
267
- method: "GET",
268
- query,
269
- secure: true,
270
- format: "json"
271
- }, params)) };
265
+ this.internalUser = {
266
+ list: (query, params = {}) => this.request(require_objectSpread2._objectSpread2({
267
+ path: `/v1/internal/user`,
268
+ method: "GET",
269
+ query,
270
+ secure: true,
271
+ format: "json"
272
+ }, params)),
273
+ resolve: (data, params = {}) => this.request(require_objectSpread2._objectSpread2({
274
+ path: `/v1/internal/user/resolve`,
275
+ method: "POST",
276
+ body: data,
277
+ secure: true,
278
+ type: ContentType.Json,
279
+ format: "json"
280
+ }, params))
281
+ };
282
+ this.internalTerms = {
283
+ status: (data, params = {}) => this.request(require_objectSpread2._objectSpread2({
284
+ path: `/v1/internal/terms/status`,
285
+ method: "POST",
286
+ body: data,
287
+ secure: true,
288
+ type: ContentType.Json,
289
+ format: "json"
290
+ }, params)),
291
+ accept: (data, params = {}) => this.request(require_objectSpread2._objectSpread2({
292
+ path: `/v1/internal/terms/accept`,
293
+ method: "POST",
294
+ body: data,
295
+ secure: true,
296
+ type: ContentType.Json,
297
+ format: "json"
298
+ }, params))
299
+ };
272
300
  this.internalGlobalAgent = {
273
301
  listGlobalAgents: (params = {}) => this.request(require_objectSpread2._objectSpread2({
274
302
  path: `/v1/internal/global-agent`,
@@ -705,6 +733,29 @@ var Api = class extends HttpClient {
705
733
  format: "json"
706
734
  }, params))
707
735
  };
736
+ this.mcpOauth = {
737
+ getGrants: (query, params = {}) => this.request(require_objectSpread2._objectSpread2({
738
+ path: `/v1/mcp-oauth/grants`,
739
+ method: "GET",
740
+ query,
741
+ secure: true,
742
+ format: "json"
743
+ }, params)),
744
+ postGrantsRevokeAll: (params = {}) => this.request(require_objectSpread2._objectSpread2({
745
+ path: `/v1/mcp-oauth/grants/revoke-all`,
746
+ method: "POST",
747
+ secure: true,
748
+ format: "json"
749
+ }, params)),
750
+ "postGrants{id}Revoke": (id, data, params = {}) => this.request(require_objectSpread2._objectSpread2({
751
+ path: `/v1/mcp-oauth/grants/${id}/revoke`,
752
+ method: "POST",
753
+ body: data,
754
+ secure: true,
755
+ type: ContentType.Json,
756
+ format: "json"
757
+ }, params))
758
+ };
708
759
  this.savedPrompts = {
709
760
  list: (params = {}) => this.request(require_objectSpread2._objectSpread2({
710
761
  path: `/v1/saved-prompts`,
@@ -755,6 +806,12 @@ var Api = class extends HttpClient {
755
806
  method: "POST",
756
807
  secure: true,
757
808
  format: "json"
809
+ }, params)),
810
+ createEmbedToken: (params = {}) => this.request(require_objectSpread2._objectSpread2({
811
+ path: `/v1/session/embed-token`,
812
+ method: "POST",
813
+ secure: true,
814
+ format: "json"
758
815
  }, params))
759
816
  };
760
817
  this.usage = {