@downcity/city 0.2.93 → 0.2.100
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/bin/index.d.ts +4 -2
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +2 -1
- package/bin/index.js.map +1 -1
- package/bin/service/ai/ai-service.d.ts +13 -9
- package/bin/service/ai/ai-service.d.ts.map +1 -1
- package/bin/service/ai/ai-service.js +113 -278
- package/bin/service/ai/ai-service.js.map +1 -1
- package/bin/service/ai/charge.d.ts +7 -0
- package/bin/service/ai/charge.d.ts.map +1 -1
- package/bin/service/ai/helpers.d.ts +0 -5
- package/bin/service/ai/helpers.d.ts.map +1 -1
- package/bin/service/ai/helpers.js +0 -9
- package/bin/service/ai/helpers.js.map +1 -1
- package/bin/service/ai/job-types.d.ts +41 -66
- package/bin/service/ai/job-types.d.ts.map +1 -1
- package/bin/service/ai/job-types.js +4 -3
- package/bin/service/ai/job-types.js.map +1 -1
- package/bin/service/ai/provider.d.ts +14 -7
- package/bin/service/ai/provider.d.ts.map +1 -1
- package/bin/service/ai/provider.js +3 -2
- package/bin/service/ai/provider.js.map +1 -1
- package/bin/service/ai/types.d.ts +8 -5
- package/bin/service/ai/types.d.ts.map +1 -1
- package/bin/service/{ai → async-job}/schema.d.ts +139 -31
- package/bin/service/async-job/schema.d.ts.map +1 -0
- package/bin/service/{ai → async-job}/schema.js +26 -14
- package/bin/service/async-job/schema.js.map +1 -0
- package/bin/types/AsyncJob.d.ts +42 -0
- package/bin/types/AsyncJob.d.ts.map +1 -0
- package/bin/types/AsyncJob.js +8 -0
- package/bin/types/AsyncJob.js.map +1 -0
- package/package.json +1 -1
- package/bin/service/ai/schema.d.ts.map +0 -1
- package/bin/service/ai/schema.js.map +0 -1
package/bin/index.d.ts
CHANGED
|
@@ -14,13 +14,14 @@ export { InstallableService } from "./service/installable-service.js";
|
|
|
14
14
|
export { Action } from "./service/action.js";
|
|
15
15
|
export type { Context, RouteAuth, EnvRequirement, } from "./service/service.js";
|
|
16
16
|
export type { RuntimeMetering, } from "./types/Metering.js";
|
|
17
|
+
export type { AsyncJobRecord, AsyncJobStatus, } from "./types/AsyncJob.js";
|
|
17
18
|
export type { ActionFn, HookFn, } from "./service/action.js";
|
|
18
19
|
export type { ServiceDefinition, ServiceInstallContext, ServiceRouteContext, } from "./service/installable-service.js";
|
|
19
20
|
export type { InstructionDefinition, InstructionContext, InstructionActionDefinition, InstructionCapable, } from "./service/instruction.js";
|
|
20
21
|
export { AIService } from "./service/ai/ai-service.js";
|
|
21
22
|
export { Provider } from "./service/ai/provider.js";
|
|
22
23
|
export type { AIServiceOptions, ModelConfig, ModelActions, OpenAICompatibleClient, OpenAICompatibleClientConfig, ProviderOptions, PublicModel, } from "./service/ai/types.js";
|
|
23
|
-
export { buildAssistantMessage, buildImageMessage, buildToolSet, isRecord, normalizeAIUsage, normalizeOpenAICompatibleBody, readErrorMessage,
|
|
24
|
+
export { buildAssistantMessage, buildImageMessage, buildToolSet, isRecord, normalizeAIUsage, normalizeOpenAICompatibleBody, readErrorMessage, readJsonResponse, readOpenAICompatibleSseUsage, readRequiredEnv, readString, resolveUpstreamModel, stripUndefined, toRecord, trimTrailingSlash, } from "./service/ai/helpers.js";
|
|
24
25
|
export type { BuildAssistantMessageResult, ExtractedImage, ToolCallShape, } from "./service/ai/helpers.js";
|
|
25
26
|
export type { AIBalanceBridge, AIBalanceChargeInput, AIProviderBillFn, AIProviderChargedOutput, AIProviderChargedResponse, AIProviderChargeLine, } from "./service/ai/charge.js";
|
|
26
27
|
export { TokenSigner } from "./federation/auth/token-signer.js";
|
|
@@ -40,7 +41,7 @@ export { AIInvoker, ModelCatalog, ModelHandle } from "./pact/invoker/ai/index.js
|
|
|
40
41
|
export { PaymentInvoker, PaymentMethodHandle } from "./pact/invoker/payment/index.js";
|
|
41
42
|
export { ServiceClient, ActionClient } from "./pact/invoker/invoker.js";
|
|
42
43
|
export type { UserPaymentMethod, UserPaymentMethodReason, UserPaymentMethodType, UserImageContent, UserImageFileContent, UserImageInput, UserImageJobCreateResult, UserImageJobResult, UserImageJobResultInput, UserImageJobStatus, UserImageMessage, UserImageResult, UserImageTextContent, UserServiceInput, UserServiceSummary, UserStreamChunk, UserStreamResult, UserTextResult, UserVideoResult, } from "./pact/user/types.js";
|
|
43
|
-
export type {
|
|
44
|
+
export type { AIImageProviderCreateResult, AIImageProviderPersistResult, AIImageProviderResult, } from "./service/ai/job-types.js";
|
|
44
45
|
export type { UserPaymentMethod as PaymentMethod, UserPaymentMethodReason as PaymentMethodReason, UserPaymentMethodType as PaymentMethodType, } from "./pact/invoker/payment/types.js";
|
|
45
46
|
export type { UserModelRef, UserModelInput, } from "./pact/invoker/ai/types.js";
|
|
46
47
|
export { BalanceInvoker, BalanceRedeemCodeInvoker } from "./pact/invoker/balance/index.js";
|
|
@@ -50,5 +51,6 @@ export type { AdminInstructionResult, AdminModelRecord, AdminServiceSummary, } f
|
|
|
50
51
|
export type { BalanceAccountRecord, BalanceHistoryListInput, BalanceLedgerRecord, BalanceMutationInput, BalanceRedeemCodeCreateInput, BalanceRedeemCodeDisableInput, BalanceRedeemCodeIssueResult, BalanceRedeemCodeListInput, BalanceRedeemCodeRecord, BalanceTopupListInput, BalanceTopupRecord, BalanceTopupUpdateInput, } from "./pact/invoker/balance/types.js";
|
|
51
52
|
export type { CityCreateInput, CityRecord, TokenApplyInput, TokenApplyResult, } from "./pact/invoker/cities/types.js";
|
|
52
53
|
export { sqliteEnv, pgEnv } from "./service/env/schema.js";
|
|
54
|
+
export { sqliteAsyncJobs, pgAsyncJobs } from "./service/async-job/schema.js";
|
|
53
55
|
export { randomSecret, base64UrlEncode, base64UrlDecode, base64UrlEncodeBytes, base64UrlDecodeBytes, timingSafeEqualBytes, httpError, normalizeEnvKey, bearerToken, parseDotenvEntries, } from "./utils/helpers.js";
|
|
54
56
|
//# sourceMappingURL=index.d.ts.map
|
package/bin/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACvF,YAAY,EAAE,8BAA8B,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAC/G,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAM7F,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,YAAY,EACV,OAAO,EACP,SAAS,EACT,cAAc,GACf,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,QAAQ,EACR,MAAM,GACP,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,kCAAkC,CAAC;AAE1C,YAAY,EACV,qBAAqB,EACrB,kBAAkB,EAClB,2BAA2B,EAC3B,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,4BAA4B,EAC5B,eAAe,EACf,WAAW,GACZ,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,6BAA6B,EAC7B,gBAAgB,EAChB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/bin/index.js
CHANGED
|
@@ -17,7 +17,7 @@ export { InstallableService } from "./service/installable-service.js";
|
|
|
17
17
|
export { Action } from "./service/action.js";
|
|
18
18
|
export { AIService } from "./service/ai/ai-service.js";
|
|
19
19
|
export { Provider } from "./service/ai/provider.js";
|
|
20
|
-
export { buildAssistantMessage, buildImageMessage, buildToolSet, isRecord, normalizeAIUsage, normalizeOpenAICompatibleBody, readErrorMessage,
|
|
20
|
+
export { buildAssistantMessage, buildImageMessage, buildToolSet, isRecord, normalizeAIUsage, normalizeOpenAICompatibleBody, readErrorMessage, readJsonResponse, readOpenAICompatibleSseUsage, readRequiredEnv, readString, resolveUpstreamModel, stripUndefined, toRecord, trimTrailingSlash, } from "./service/ai/helpers.js";
|
|
21
21
|
// ===========================================================================
|
|
22
22
|
// 场景 3:用户鉴权与 Token
|
|
23
23
|
// ===========================================================================
|
|
@@ -39,6 +39,7 @@ export { EnvInvoker } from "./pact/invoker/env/index.js";
|
|
|
39
39
|
// 场景 7:内置表 Schema
|
|
40
40
|
// ===========================================================================
|
|
41
41
|
export { sqliteEnv, pgEnv } from "./service/env/schema.js";
|
|
42
|
+
export { sqliteAsyncJobs, pgAsyncJobs } from "./service/async-job/schema.js";
|
|
42
43
|
// ===========================================================================
|
|
43
44
|
// 场景 8:工具函数
|
|
44
45
|
// ===========================================================================
|
package/bin/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAKxD,8EAA8E;AAC9E,+CAA+C;AAC/C,8EAA8E;AAE9E,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAKxD,8EAA8E;AAC9E,+CAA+C;AAC/C,8EAA8E;AAE9E,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAmC7C,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAYpD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,6BAA6B,EAC7B,gBAAgB,EAChB,gBAAgB,EAChB,4BAA4B,EAC5B,eAAe,EACf,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,QAAQ,EACR,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AAiBjC,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAShE,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAQ1D,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAOtD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAqB3C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAUxC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAyCxE,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3F,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AA8BzD,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAE7E,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,OAAO,EACL,YAAY,EACZ,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,SAAS,EACT,eAAe,EACf,WAAW,EACX,kBAAkB,GACnB,MAAM,oBAAoB,CAAC"}
|
|
@@ -9,14 +9,17 @@
|
|
|
9
9
|
* 路由(City 自动生成):
|
|
10
10
|
* - POST /v1/ai/text — 文本生成
|
|
11
11
|
* - POST /v1/ai/stream — 流式生成
|
|
12
|
-
* - POST /v1/ai/image — 图片生成
|
|
13
12
|
* - POST /v1/ai/video — 视频生成
|
|
13
|
+
* - POST /v1/ai/image/create — 创建图片生成任务
|
|
14
|
+
* - POST /v1/ai/image/result — 查询图片生成任务
|
|
15
|
+
* - POST /v1/ai/image/persist — 后台持久化图片结果(admin)
|
|
14
16
|
* - POST /v1/ai/chat/completions — OpenAI 兼容端点
|
|
15
17
|
* - GET /v1/ai/models — 模型列表
|
|
16
18
|
*/
|
|
17
|
-
import { Service } from "../service.js";
|
|
19
|
+
import { Service, type Context } from "../service.js";
|
|
18
20
|
import type { ActionFn } from "../action.js";
|
|
19
21
|
import type { AIServiceOptions, ModelConfig, PublicModel } from "./types.js";
|
|
22
|
+
import type { AIImageProviderPersistResult } from "./job-types.js";
|
|
20
23
|
type EnvReader = (key: string) => string | undefined;
|
|
21
24
|
export declare class AIService extends Service {
|
|
22
25
|
/** 模型注册表 */
|
|
@@ -49,13 +52,14 @@ export declare class AIService extends Service {
|
|
|
49
52
|
private handleModality;
|
|
50
53
|
private createImageJob;
|
|
51
54
|
private readImageJob;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
/**
|
|
56
|
+
* 持久化图片任务结果,并在持久化成功后触发计费。
|
|
57
|
+
*
|
|
58
|
+
* 关键点(中文)
|
|
59
|
+
* - 这个方法供后台 worker / queue / cron 调用,不是前端轮询 API。
|
|
60
|
+
* - Provider 负责把结果写入自己的存储;AIService 只在 succeeded 后执行 model.bill。
|
|
61
|
+
*/
|
|
62
|
+
persistImageJob(ctx: Context): Promise<AIImageProviderPersistResult>;
|
|
59
63
|
private handleChatCompletions;
|
|
60
64
|
/**
|
|
61
65
|
* 将解析出的模型写回原始 Context,供 hook / usage / charge 读取。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-service.d.ts","sourceRoot":"","sources":["../../../src/service/ai/ai-service.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ai-service.d.ts","sourceRoot":"","sources":["../../../src/service/ai/ai-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EACV,gBAAgB,EAEhB,WAAW,EAEX,WAAW,EACZ,MAAM,YAAY,CAAC;AAOpB,OAAO,KAAK,EAEV,4BAA4B,EAI7B,MAAM,gBAAgB,CAAC;AAUxB,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAmJrD,qBAAa,SAAU,SAAQ,OAAO;IACpC,YAAY;IACZ,OAAO,CAAC,QAAQ,CAAkC;IAElD,0CAA0C;IAC1C,OAAO,CAAC,eAAe,CAA+B;IAEtD,iBAAiB;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkB;gBAE/B,OAAO,GAAE,gBAAqB;IAsC1C,GAAG,CAAC,GAAG,MAAM,EAAE,CAAC,WAAW,GAAG,WAAW,EAAE,CAAC,EAAE,GAAG,IAAI;IAarD,UAAU,IAAI,WAAW,EAAE;IAI3B,SAAS,IAAI,OAAO;IAMpB,OAAO,CAAC,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG;QAAE,KAAK,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,QAAQ,CAAA;KAAE;IA2B7G,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,2BAA2B;IAmCnC,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,uBAAuB;IAc/B,OAAO,CAAC,aAAa;YAQP,cAAc;YAuBd,cAAc;YAcd,YAAY;IAc1B;;;;;;OAMG;IACG,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,4BAA4B,CAAC;YAsB5D,qBAAqB;IAwBnC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAkB3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAgB5B;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IAgB7B;;OAEG;YACW,YAAY;IAkC1B,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE;QAC/C,GAAG,EAAE,SAAS,CAAC;QACf,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;KACtC,GAAG,WAAW,EAAE;IAYjB,OAAO,CAAC,aAAa;CAgBtB"}
|
|
@@ -9,21 +9,22 @@
|
|
|
9
9
|
* 路由(City 自动生成):
|
|
10
10
|
* - POST /v1/ai/text — 文本生成
|
|
11
11
|
* - POST /v1/ai/stream — 流式生成
|
|
12
|
-
* - POST /v1/ai/image — 图片生成
|
|
13
12
|
* - POST /v1/ai/video — 视频生成
|
|
13
|
+
* - POST /v1/ai/image/create — 创建图片生成任务
|
|
14
|
+
* - POST /v1/ai/image/result — 查询图片生成任务
|
|
15
|
+
* - POST /v1/ai/image/persist — 后台持久化图片结果(admin)
|
|
14
16
|
* - POST /v1/ai/chat/completions — OpenAI 兼容端点
|
|
15
17
|
* - GET /v1/ai/models — 模型列表
|
|
16
18
|
*/
|
|
17
19
|
import { Service } from "../service.js";
|
|
18
|
-
import { httpError
|
|
19
|
-
import { sqliteAIImageJobs } from "./schema.js";
|
|
20
|
+
import { httpError } from "../../utils/helpers.js";
|
|
20
21
|
import { normalizeAIUsage } from "./helpers.js";
|
|
21
|
-
/** AIService 直接暴露的 SDK
|
|
22
|
-
const MODALITIES = ["text", "stream", "
|
|
22
|
+
/** AIService 直接暴露的 SDK 通路模态列表。图片只通过 image/create + image/result 暴露。 */
|
|
23
|
+
const MODALITIES = ["text", "stream", "video", "tts", "asr"];
|
|
23
24
|
/** 用户侧默认以 text 模态排序模型 */
|
|
24
25
|
const DEFAULT_MODEL_MODE = "text";
|
|
25
|
-
/**
|
|
26
|
-
const
|
|
26
|
+
/** 图片任务的内部 action 列表。 */
|
|
27
|
+
const IMAGE_ACTION_MODES = ["image_create", "image_persist", "image_result"];
|
|
27
28
|
/**
|
|
28
29
|
* 判断一个值是否为 HTTP Response。
|
|
29
30
|
*/
|
|
@@ -54,15 +55,6 @@ function isProviderChargedOutput(value) {
|
|
|
54
55
|
function isPromiseLike(value) {
|
|
55
56
|
return Boolean(value && typeof value === "object" && "then" in value && typeof value.then === "function");
|
|
56
57
|
}
|
|
57
|
-
/**
|
|
58
|
-
* 读取必填字符串。
|
|
59
|
-
*/
|
|
60
|
-
function readRequiredString(value, label) {
|
|
61
|
-
const text = readOptionalString(value);
|
|
62
|
-
if (!text)
|
|
63
|
-
throw httpError(422, `${label} is required`);
|
|
64
|
-
return text;
|
|
65
|
-
}
|
|
66
58
|
/**
|
|
67
59
|
* 读取可选字符串。
|
|
68
60
|
*/
|
|
@@ -70,57 +62,13 @@ function readOptionalString(value) {
|
|
|
70
62
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
71
63
|
}
|
|
72
64
|
/**
|
|
73
|
-
*
|
|
74
|
-
*/
|
|
75
|
-
async function normalizeImageJobOutput(output) {
|
|
76
|
-
if (!isResponse(output))
|
|
77
|
-
return output;
|
|
78
|
-
const text = await output.text();
|
|
79
|
-
if (!output.ok) {
|
|
80
|
-
throw httpError(output.status, text || output.statusText || "image generation failed");
|
|
81
|
-
}
|
|
82
|
-
return text ? JSON.parse(text) : {};
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* 解析已持久化的图片任务结果。
|
|
86
|
-
*/
|
|
87
|
-
function parseImageJobResult(raw) {
|
|
88
|
-
try {
|
|
89
|
-
return JSON.parse(raw);
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* 解析可恢复任务状态。
|
|
65
|
+
* 保留已知 HTTP 错误状态,其它异常统一包装成上游错误。
|
|
97
66
|
*/
|
|
98
|
-
function
|
|
99
|
-
if (
|
|
100
|
-
return
|
|
101
|
-
try {
|
|
102
|
-
const parsed = JSON.parse(raw);
|
|
103
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
104
|
-
? parsed
|
|
105
|
-
: undefined;
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* 解析已持久化的图片任务输入。
|
|
113
|
-
*/
|
|
114
|
-
function parseImageJobInput(raw) {
|
|
115
|
-
try {
|
|
116
|
-
const parsed = JSON.parse(raw);
|
|
117
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
118
|
-
? parsed
|
|
119
|
-
: {};
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
return {};
|
|
67
|
+
function imageActionError(error, fallback_message) {
|
|
68
|
+
if (error instanceof Error && typeof error.statusCode === "number") {
|
|
69
|
+
return error;
|
|
123
70
|
}
|
|
71
|
+
return httpError(502, error instanceof Error ? error.message : fallback_message);
|
|
124
72
|
}
|
|
125
73
|
/**
|
|
126
74
|
* 从输出对象中读取 provider usage。
|
|
@@ -159,51 +107,60 @@ function countImageOutputs(output) {
|
|
|
159
107
|
return count > 0 ? count : undefined;
|
|
160
108
|
}
|
|
161
109
|
/**
|
|
162
|
-
* 判断 provider
|
|
110
|
+
* 判断 provider 是否返回了图片任务创建结果。
|
|
163
111
|
*/
|
|
164
|
-
function
|
|
112
|
+
function isImageProviderCreateResult(value) {
|
|
165
113
|
if (!value || typeof value !== "object")
|
|
166
114
|
return false;
|
|
167
|
-
const
|
|
168
|
-
return
|
|
115
|
+
const record = value;
|
|
116
|
+
return typeof record.job_id === "string" &&
|
|
117
|
+
Boolean(record.job_id.trim()) &&
|
|
118
|
+
isImageJobStatus(record.status);
|
|
169
119
|
}
|
|
170
120
|
/**
|
|
171
|
-
*
|
|
121
|
+
* 判断 provider 是否返回了图片任务查询结果。
|
|
172
122
|
*/
|
|
173
|
-
function
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
city_id: readNullableString(row.city_id),
|
|
188
|
-
user_id: readNullableString(row.user_id),
|
|
189
|
-
model_id: readNullableString(row.model_id),
|
|
190
|
-
created_at: String(row.created_at ?? ""),
|
|
191
|
-
updated_at: String(row.updated_at ?? ""),
|
|
192
|
-
};
|
|
123
|
+
function isImageProviderResult(value) {
|
|
124
|
+
if (!value || typeof value !== "object")
|
|
125
|
+
return false;
|
|
126
|
+
const record = value;
|
|
127
|
+
if (typeof record.job_id !== "string" || !record.job_id.trim())
|
|
128
|
+
return false;
|
|
129
|
+
if (!isImageJobStatus(record.status))
|
|
130
|
+
return false;
|
|
131
|
+
if (record.status === "succeeded") {
|
|
132
|
+
const result = isRecord(record.result) ? record.result : undefined;
|
|
133
|
+
if (!result || result.role !== "assistant" || !Array.isArray(result.parts))
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
193
137
|
}
|
|
194
138
|
/**
|
|
195
|
-
*
|
|
139
|
+
* 判断 provider 是否返回了图片持久化结果。
|
|
196
140
|
*/
|
|
197
|
-
function
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
141
|
+
function isImageProviderPersistResult(value) {
|
|
142
|
+
if (!value || typeof value !== "object")
|
|
143
|
+
return false;
|
|
144
|
+
const record = value;
|
|
145
|
+
if (typeof record.job_id !== "string" || !record.job_id.trim())
|
|
146
|
+
return false;
|
|
147
|
+
if (!isImageJobStatus(record.status))
|
|
148
|
+
return false;
|
|
149
|
+
if (record.status === "succeeded") {
|
|
150
|
+
const result = isRecord(record.result) ? record.result : undefined;
|
|
151
|
+
if (!result || result.role !== "assistant" || !Array.isArray(result.parts))
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
201
155
|
}
|
|
202
156
|
/**
|
|
203
|
-
*
|
|
157
|
+
* 判断图片任务状态。
|
|
204
158
|
*/
|
|
205
|
-
function
|
|
206
|
-
return
|
|
159
|
+
function isImageJobStatus(value) {
|
|
160
|
+
return value === "queued" ||
|
|
161
|
+
value === "running" ||
|
|
162
|
+
value === "succeeded" ||
|
|
163
|
+
value === "failed";
|
|
207
164
|
}
|
|
208
165
|
export class AIService extends Service {
|
|
209
166
|
/** 模型注册表 */
|
|
@@ -213,7 +170,7 @@ export class AIService extends Service {
|
|
|
213
170
|
/** AI 专用余额桥接。 */
|
|
214
171
|
balance;
|
|
215
172
|
constructor(options = {}) {
|
|
216
|
-
super({ id: "ai", name: "AI"
|
|
173
|
+
super({ id: "ai", name: "AI" });
|
|
217
174
|
this.balance = options.balance;
|
|
218
175
|
// 为每个 modality 注册 routing action
|
|
219
176
|
for (const modality of MODALITIES) {
|
|
@@ -228,6 +185,9 @@ export class AIService extends Service {
|
|
|
228
185
|
this.action("image/result", async (ctx) => this.readImageJob(ctx), {
|
|
229
186
|
auth: ["user", "admin"],
|
|
230
187
|
});
|
|
188
|
+
this.action("image/persist", async (ctx) => this.persistImageJob(ctx), {
|
|
189
|
+
auth: ["admin"],
|
|
190
|
+
});
|
|
231
191
|
// OpenAI 兼容端点
|
|
232
192
|
this.action("chat/completions", async (ctx) => this.handleChatCompletions(ctx), {
|
|
233
193
|
auth: ["user", "admin"],
|
|
@@ -290,6 +250,14 @@ export class AIService extends Service {
|
|
|
290
250
|
throw httpError(422, `No model registered`);
|
|
291
251
|
}
|
|
292
252
|
getAction(model, mode) {
|
|
253
|
+
if (mode === "image" || IMAGE_ACTION_MODES.includes(mode)) {
|
|
254
|
+
const has_image_actions = Boolean(model.actions.image_create && model.actions.image_persist && model.actions.image_result);
|
|
255
|
+
if (!has_image_actions)
|
|
256
|
+
return undefined;
|
|
257
|
+
return mode === "image"
|
|
258
|
+
? model.actions.image_create
|
|
259
|
+
: model.actions[mode];
|
|
260
|
+
}
|
|
293
261
|
return model.actions[(mode ?? "text")];
|
|
294
262
|
}
|
|
295
263
|
resolveOpenAIAction(model) {
|
|
@@ -365,9 +333,12 @@ export class AIService extends Service {
|
|
|
365
333
|
}
|
|
366
334
|
getModelModalities(model) {
|
|
367
335
|
const modalities = Object.keys(model.actions).filter((key) => model.actions[key] !== undefined);
|
|
336
|
+
if (modalities.includes("image_create") && modalities.includes("image_persist") && modalities.includes("image_result")) {
|
|
337
|
+
modalities.push("image");
|
|
338
|
+
}
|
|
368
339
|
if (!modalities.includes("openai") && this.resolveOpenAIAction(model))
|
|
369
340
|
modalities.push("openai");
|
|
370
|
-
return modalities;
|
|
341
|
+
return modalities.filter((mode) => mode !== "image_create" && mode !== "image_persist" && mode !== "image_result");
|
|
371
342
|
}
|
|
372
343
|
getModelEnvRequirements(model) {
|
|
373
344
|
const requirements = model.env
|
|
@@ -410,197 +381,60 @@ export class AIService extends Service {
|
|
|
410
381
|
}
|
|
411
382
|
// ========== 图片任务通路 ==========
|
|
412
383
|
async createImageJob(ctx) {
|
|
413
|
-
const resolved = this.resolve({ model: ctx.input.model, mode: "
|
|
414
|
-
const job_id = `img_${randomSecret(12)}`;
|
|
384
|
+
const resolved = this.resolve({ model: ctx.input.model, mode: "image_create" }, ctx.env);
|
|
415
385
|
this.attachResolvedModel(ctx, resolved.model, "image/create");
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
status: "queued",
|
|
421
|
-
input_json: JSON.stringify(ctx.input),
|
|
422
|
-
result_json: null,
|
|
423
|
-
error: null,
|
|
424
|
-
message: "queued",
|
|
425
|
-
city_id: ctx.city?.city_id ?? readOptionalString(ctx.input.city_id),
|
|
426
|
-
user_id: ctx.user?.user_id ?? null,
|
|
427
|
-
model_id: resolved.model?.id ?? null,
|
|
428
|
-
created_at: now,
|
|
429
|
-
updated_at: now,
|
|
430
|
-
};
|
|
431
|
-
await this.imageJobTable(ctx).insert(recordToRow(record));
|
|
432
|
-
const job_ctx = {
|
|
433
|
-
...ctx,
|
|
434
|
-
input: { ...ctx.input },
|
|
435
|
-
locals: {},
|
|
436
|
-
output: undefined,
|
|
437
|
-
error: undefined,
|
|
438
|
-
};
|
|
439
|
-
if (resolved.model?.actions.image_job) {
|
|
440
|
-
let advanced;
|
|
441
|
-
try {
|
|
442
|
-
advanced = await this.advanceImageJob(job_ctx, record);
|
|
443
|
-
}
|
|
444
|
-
catch (error) {
|
|
445
|
-
await this.updateImageJob(ctx, job_id, {
|
|
446
|
-
status: "failed",
|
|
447
|
-
error: error instanceof Error ? error.message : String(error),
|
|
448
|
-
message: "failed",
|
|
449
|
-
updated_at: new Date().toISOString(),
|
|
450
|
-
});
|
|
451
|
-
throw httpError(502, error instanceof Error ? error.message : "image_job action failed");
|
|
386
|
+
try {
|
|
387
|
+
const created = await resolved.action(ctx);
|
|
388
|
+
if (!isImageProviderCreateResult(created)) {
|
|
389
|
+
throw httpError(500, "image_create action returned invalid result");
|
|
452
390
|
}
|
|
453
|
-
return
|
|
454
|
-
job_id,
|
|
455
|
-
status: advanced.status,
|
|
456
|
-
poll_after_ms: IMAGE_JOB_POLL_AFTER_MS,
|
|
457
|
-
};
|
|
391
|
+
return created;
|
|
458
392
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (ctx.waitUntil)
|
|
462
|
-
ctx.waitUntil(promise);
|
|
463
|
-
else
|
|
464
|
-
void promise;
|
|
393
|
+
catch (error) {
|
|
394
|
+
throw imageActionError(error, "image_create action failed");
|
|
465
395
|
}
|
|
466
|
-
return {
|
|
467
|
-
job_id,
|
|
468
|
-
status: "queued",
|
|
469
|
-
poll_after_ms: IMAGE_JOB_POLL_AFTER_MS,
|
|
470
|
-
};
|
|
471
396
|
}
|
|
472
397
|
async readImageJob(ctx) {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
if (!record)
|
|
476
|
-
throw httpError(404, `Image job not found: ${job_id}`);
|
|
477
|
-
this.ensureImageJobAccess(ctx, record);
|
|
478
|
-
if (record.status === "queued" || record.status === "running") {
|
|
479
|
-
const advanced = await this.advanceImageJob(ctx, record);
|
|
480
|
-
return this.toImageJobResult(advanced);
|
|
481
|
-
}
|
|
482
|
-
return this.toImageJobResult(record);
|
|
483
|
-
}
|
|
484
|
-
async advanceImageJob(ctx, record) {
|
|
485
|
-
const input = parseImageJobInput(record.input_json);
|
|
486
|
-
const resolved = this.resolve({ model: input.model, mode: "image" }, ctx.env);
|
|
487
|
-
const step_action = resolved.model?.actions.image_job;
|
|
488
|
-
if (!step_action)
|
|
489
|
-
return record;
|
|
490
|
-
this.attachResolvedModel(ctx, resolved.model, "image");
|
|
491
|
-
const step_ctx = {
|
|
492
|
-
...ctx,
|
|
493
|
-
input,
|
|
494
|
-
locals: {
|
|
495
|
-
...ctx.locals,
|
|
496
|
-
image_job: {
|
|
497
|
-
job_id: record.job_id,
|
|
498
|
-
status: record.status,
|
|
499
|
-
state: parseImageJobStepState(record.result_json),
|
|
500
|
-
},
|
|
501
|
-
},
|
|
502
|
-
};
|
|
503
|
-
const output = await step_action(step_ctx);
|
|
504
|
-
if (!isImageJobStepResult(output)) {
|
|
505
|
-
throw httpError(500, "image_job action returned invalid result");
|
|
506
|
-
}
|
|
507
|
-
const updated_at = new Date().toISOString();
|
|
508
|
-
if (output.status === "succeeded") {
|
|
509
|
-
if (!output.result)
|
|
510
|
-
throw httpError(500, "image_job action succeeded without result");
|
|
511
|
-
await this.updateImageJob(ctx, record.job_id, {
|
|
512
|
-
status: "succeeded",
|
|
513
|
-
result_json: JSON.stringify(output.result),
|
|
514
|
-
error: null,
|
|
515
|
-
message: output.message ?? "succeeded",
|
|
516
|
-
updated_at,
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
else if (output.status === "failed") {
|
|
520
|
-
await this.updateImageJob(ctx, record.job_id, {
|
|
521
|
-
status: "failed",
|
|
522
|
-
error: output.error ?? output.message ?? "image generation failed",
|
|
523
|
-
message: output.message ?? "failed",
|
|
524
|
-
updated_at,
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
else {
|
|
528
|
-
await this.updateImageJob(ctx, record.job_id, {
|
|
529
|
-
status: "running",
|
|
530
|
-
result_json: output.state ? JSON.stringify(output.state) : record.result_json,
|
|
531
|
-
error: null,
|
|
532
|
-
message: output.message ?? "running",
|
|
533
|
-
updated_at,
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
const next = await this.getImageJob(ctx, record.job_id);
|
|
537
|
-
return next ?? record;
|
|
538
|
-
}
|
|
539
|
-
async runImageJob(job_id, ctx) {
|
|
398
|
+
const resolved = this.resolve({ model: ctx.input.model, mode: "image_result" }, ctx.env);
|
|
399
|
+
this.attachResolvedModel(ctx, resolved.model, "image/result");
|
|
540
400
|
try {
|
|
541
|
-
await this.updateImageJob(ctx, job_id, {
|
|
542
|
-
status: "running",
|
|
543
|
-
message: "running",
|
|
544
|
-
error: null,
|
|
545
|
-
updated_at: new Date().toISOString(),
|
|
546
|
-
});
|
|
547
|
-
const resolved = this.resolve({ model: ctx.input.model, mode: "image" }, ctx.env);
|
|
548
|
-
this.attachResolvedModel(ctx, resolved.model, "image");
|
|
549
401
|
const output = await resolved.action(ctx);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
error: null,
|
|
555
|
-
message: "succeeded",
|
|
556
|
-
updated_at: new Date().toISOString(),
|
|
557
|
-
});
|
|
402
|
+
if (!isImageProviderResult(output)) {
|
|
403
|
+
throw httpError(500, "image_result action returned invalid result");
|
|
404
|
+
}
|
|
405
|
+
return output;
|
|
558
406
|
}
|
|
559
407
|
catch (error) {
|
|
560
|
-
|
|
561
|
-
status: "failed",
|
|
562
|
-
error: error instanceof Error ? error.message : String(error),
|
|
563
|
-
message: "failed",
|
|
564
|
-
updated_at: new Date().toISOString(),
|
|
565
|
-
});
|
|
408
|
+
throw imageActionError(error, "image_result action failed");
|
|
566
409
|
}
|
|
567
410
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
411
|
+
/**
|
|
412
|
+
* 持久化图片任务结果,并在持久化成功后触发计费。
|
|
413
|
+
*
|
|
414
|
+
* 关键点(中文)
|
|
415
|
+
* - 这个方法供后台 worker / queue / cron 调用,不是前端轮询 API。
|
|
416
|
+
* - Provider 负责把结果写入自己的存储;AIService 只在 succeeded 后执行 model.bill。
|
|
417
|
+
*/
|
|
418
|
+
async persistImageJob(ctx) {
|
|
419
|
+
const resolved = this.resolve({ model: ctx.input.model, mode: "image_persist" }, ctx.env);
|
|
420
|
+
this.attachResolvedModel(ctx, resolved.model, "image/persist");
|
|
421
|
+
const started_at = Date.now();
|
|
422
|
+
try {
|
|
423
|
+
const output = await resolved.action(ctx);
|
|
424
|
+
if (!isImageProviderPersistResult(output)) {
|
|
425
|
+
throw httpError(500, "image_persist action returned invalid result");
|
|
426
|
+
}
|
|
427
|
+
if (output.status === "succeeded") {
|
|
428
|
+
this.attachOutputMetering(ctx, output.result, "image", started_at);
|
|
429
|
+
const charge = resolved.model?.bill?.(ctx, output);
|
|
430
|
+
await this.handleCharge(ctx, charge, isPromiseLike(charge));
|
|
431
|
+
}
|
|
432
|
+
return output;
|
|
586
433
|
}
|
|
587
|
-
|
|
588
|
-
throw
|
|
434
|
+
catch (error) {
|
|
435
|
+
throw imageActionError(error, "image_persist action failed");
|
|
589
436
|
}
|
|
590
437
|
}
|
|
591
|
-
toImageJobResult(record) {
|
|
592
|
-
const result = record.status === "succeeded" && record.result_json
|
|
593
|
-
? parseImageJobResult(record.result_json)
|
|
594
|
-
: undefined;
|
|
595
|
-
return {
|
|
596
|
-
job_id: record.job_id,
|
|
597
|
-
status: record.status,
|
|
598
|
-
result,
|
|
599
|
-
error: record.error ?? undefined,
|
|
600
|
-
message: record.message ?? undefined,
|
|
601
|
-
poll_after_ms: IMAGE_JOB_POLL_AFTER_MS,
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
438
|
// ========== OpenAI 兼容通路 ==========
|
|
605
439
|
async handleChatCompletions(ctx) {
|
|
606
440
|
const body = ctx.input;
|
|
@@ -697,11 +531,12 @@ export class AIService extends Service {
|
|
|
697
531
|
.then(async (line) => {
|
|
698
532
|
if (!line || line.amount_microcredits <= 0)
|
|
699
533
|
return;
|
|
700
|
-
|
|
534
|
+
const user_id = line.user_id ?? ctx.user?.user_id;
|
|
535
|
+
if (!user_id)
|
|
701
536
|
return;
|
|
702
537
|
await this.balance?.charge({
|
|
703
|
-
user_id: ctx.user.user_id,
|
|
704
538
|
...line,
|
|
539
|
+
user_id,
|
|
705
540
|
});
|
|
706
541
|
});
|
|
707
542
|
if (!defer) {
|