@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.
Files changed (35) hide show
  1. package/bin/index.d.ts +4 -2
  2. package/bin/index.d.ts.map +1 -1
  3. package/bin/index.js +2 -1
  4. package/bin/index.js.map +1 -1
  5. package/bin/service/ai/ai-service.d.ts +13 -9
  6. package/bin/service/ai/ai-service.d.ts.map +1 -1
  7. package/bin/service/ai/ai-service.js +113 -278
  8. package/bin/service/ai/ai-service.js.map +1 -1
  9. package/bin/service/ai/charge.d.ts +7 -0
  10. package/bin/service/ai/charge.d.ts.map +1 -1
  11. package/bin/service/ai/helpers.d.ts +0 -5
  12. package/bin/service/ai/helpers.d.ts.map +1 -1
  13. package/bin/service/ai/helpers.js +0 -9
  14. package/bin/service/ai/helpers.js.map +1 -1
  15. package/bin/service/ai/job-types.d.ts +41 -66
  16. package/bin/service/ai/job-types.d.ts.map +1 -1
  17. package/bin/service/ai/job-types.js +4 -3
  18. package/bin/service/ai/job-types.js.map +1 -1
  19. package/bin/service/ai/provider.d.ts +14 -7
  20. package/bin/service/ai/provider.d.ts.map +1 -1
  21. package/bin/service/ai/provider.js +3 -2
  22. package/bin/service/ai/provider.js.map +1 -1
  23. package/bin/service/ai/types.d.ts +8 -5
  24. package/bin/service/ai/types.d.ts.map +1 -1
  25. package/bin/service/{ai → async-job}/schema.d.ts +139 -31
  26. package/bin/service/async-job/schema.d.ts.map +1 -0
  27. package/bin/service/{ai → async-job}/schema.js +26 -14
  28. package/bin/service/async-job/schema.js.map +1 -0
  29. package/bin/types/AsyncJob.d.ts +42 -0
  30. package/bin/types/AsyncJob.d.ts.map +1 -0
  31. package/bin/types/AsyncJob.js +8 -0
  32. package/bin/types/AsyncJob.js.map +1 -0
  33. package/package.json +1 -1
  34. package/bin/service/ai/schema.d.ts.map +0 -1
  35. 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, readImageJobStepContext, readJsonResponse, readOpenAICompatibleSseUsage, readRequiredEnv, readString, resolveUpstreamModel, stripUndefined, toRecord, trimTrailingSlash, } from "./service/ai/helpers.js";
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 { AIImageJobStepContext, AIImageJobStepResult, AIImageJobStepState, } from "./service/ai/job-types.js";
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
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, readImageJobStepContext, readJsonResponse, readOpenAICompatibleSseUsage, readRequiredEnv, readString, resolveUpstreamModel, stripUndefined, toRecord, trimTrailingSlash, } from "./service/ai/helpers.js";
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;AA8B7C,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,uBAAuB,EACvB,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;AAE3D,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"}
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
- private advanceImageJob;
53
- private runImageJob;
54
- private imageJobTable;
55
- private getImageJob;
56
- private updateImageJob;
57
- private ensureImageJobAccess;
58
- private toImageJobResult;
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;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAgB,MAAM,eAAe,CAAC;AAEtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI7C,OAAO,KAAK,EACV,gBAAgB,EAEhB,WAAW,EAEX,WAAW,EACZ,MAAM,YAAY,CAAC;AAwBpB,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAsMrD,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;IAmC1C,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;IAIjB,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;IAM1B,OAAO,CAAC,uBAAuB;IAc/B,OAAO,CAAC,aAAa;YAQP,cAAc;YAuBd,cAAc;YA6Dd,YAAY;YAYZ,eAAe;YAuDf,WAAW;IA+BzB,OAAO,CAAC,aAAa;YAMP,WAAW;YAKX,cAAc;IAQ5B,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,gBAAgB;YAgBV,qBAAqB;IAwBnC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAkB3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAgB5B;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IAgB7B;;OAEG;YACW,YAAY;IAiC1B,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"}
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, randomSecret } from "../../utils/helpers.js";
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", "image", "video", "tts", "asr"];
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 IMAGE_JOB_POLL_AFTER_MS = 2_000;
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 parseImageJobStepState(raw) {
99
- if (!raw)
100
- return undefined;
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 isImageJobStepResult(value) {
112
+ function isImageProviderCreateResult(value) {
165
113
  if (!value || typeof value !== "object")
166
114
  return false;
167
- const status = value.status;
168
- return status === "running" || status === "succeeded" || status === "failed";
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
- * 把类型化任务记录转成 TableApi 使用的普通行。
121
+ * 判断 provider 是否返回了图片任务查询结果。
172
122
  */
173
- function recordToRow(record) {
174
- return { ...record };
175
- }
176
- /**
177
- * TableApi 普通行转成图片任务记录。
178
- */
179
- function rowToImageJobRecord(row) {
180
- return {
181
- job_id: String(row.job_id ?? ""),
182
- status: readJobStatus(row.status),
183
- input_json: String(row.input_json ?? "{}"),
184
- result_json: readNullableString(row.result_json),
185
- error: readNullableString(row.error),
186
- message: readNullableString(row.message),
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 readJobStatus(value) {
198
- return value === "queued" || value === "running" || value === "succeeded" || value === "failed"
199
- ? value
200
- : "failed";
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 readNullableString(value) {
206
- return typeof value === "string" ? value : null;
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", tables: { image_jobs: sqliteAIImageJobs } });
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: "image" }, ctx.env);
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
- await this.handleCharge(ctx, resolved.model?.bill?.(ctx, { job_id }), false);
417
- const now = new Date().toISOString();
418
- const record = {
419
- job_id,
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
- else {
460
- const promise = this.runImageJob(job_id, job_ctx);
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 job_id = readRequiredString(ctx.input.job_id, "job_id");
474
- const record = await this.getImageJob(ctx, job_id);
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
- const result = await normalizeImageJobOutput(output);
551
- await this.updateImageJob(ctx, job_id, {
552
- status: "succeeded",
553
- result_json: JSON.stringify(result),
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
- await this.updateImageJob(ctx, job_id, {
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
- imageJobTable(ctx) {
569
- const table = ctx.db.image_jobs;
570
- if (!table)
571
- throw httpError(500, "AI image job table is not initialized");
572
- return table;
573
- }
574
- async getImageJob(ctx, job_id) {
575
- const rows = await this.imageJobTable(ctx).select({ job_id });
576
- return rows[0] ? rowToImageJobRecord(rows[0]) : undefined;
577
- }
578
- async updateImageJob(ctx, job_id, values) {
579
- await this.imageJobTable(ctx).update({ where: { job_id }, values: recordToRow(values) });
580
- }
581
- ensureImageJobAccess(ctx, record) {
582
- if (ctx.identity?.kind !== "user")
583
- return;
584
- if (record.city_id && ctx.city?.city_id && record.city_id !== ctx.city.city_id) {
585
- throw httpError(404, `Image job not found: ${record.job_id}`);
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
- if (record.user_id && ctx.user?.user_id && record.user_id !== ctx.user.user_id) {
588
- throw httpError(404, `Image job not found: ${record.job_id}`);
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
- if (!ctx.user?.user_id)
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) {