@downcity/city 0.2.198 → 0.2.205
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 +31 -13
- package/bin/index.d.ts +4 -8
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +3 -3
- package/bin/index.js.map +1 -1
- package/bin/pact/invoker/ai/CityModel.d.ts +2 -1
- package/bin/pact/invoker/ai/CityModel.d.ts.map +1 -1
- package/bin/pact/invoker/ai/CityModel.js +5 -65
- package/bin/pact/invoker/ai/CityModel.js.map +1 -1
- package/bin/pact/invoker/ai/client-stream.js +2 -2
- package/bin/pact/invoker/ai/client-stream.js.map +1 -1
- package/bin/pact/invoker/ai/index.d.ts +5 -61
- package/bin/pact/invoker/ai/index.d.ts.map +1 -1
- package/bin/pact/invoker/ai/index.js +5 -86
- package/bin/pact/invoker/ai/index.js.map +1 -1
- package/bin/pact/invoker/ai/types.d.ts +1 -1
- package/bin/pact/invoker/ai/types.js +1 -1
- package/bin/pact/invoker/invoker.d.ts +1 -1
- package/bin/pact/invoker/invoker.js +1 -1
- package/bin/pact/invoker/payment/index.d.ts +2 -2
- package/bin/pact/invoker/payment/index.js +2 -2
- package/bin/pact/user/index.d.ts.map +1 -1
- package/bin/pact/user/index.js +0 -1
- package/bin/pact/user/index.js.map +1 -1
- package/bin/pact/user/types.d.ts +11 -4
- package/bin/pact/user/types.d.ts.map +1 -1
- package/bin/service/ai/AIChannel.d.ts +57 -0
- package/bin/service/ai/AIChannel.d.ts.map +1 -0
- package/bin/service/ai/AIChannel.js +151 -0
- package/bin/service/ai/AIChannel.js.map +1 -0
- package/bin/service/ai/OpenAIChatCompletionsAdapter.d.ts +23 -0
- package/bin/service/ai/OpenAIChatCompletionsAdapter.d.ts.map +1 -0
- package/bin/service/ai/OpenAIChatCompletionsAdapter.js +503 -0
- package/bin/service/ai/OpenAIChatCompletionsAdapter.js.map +1 -0
- package/bin/service/ai/ai-service-values.d.ts +16 -19
- package/bin/service/ai/ai-service-values.d.ts.map +1 -1
- package/bin/service/ai/ai-service-values.js +9 -13
- package/bin/service/ai/ai-service-values.js.map +1 -1
- package/bin/service/ai/ai-service.d.ts +15 -14
- package/bin/service/ai/ai-service.d.ts.map +1 -1
- package/bin/service/ai/ai-service.js +97 -106
- package/bin/service/ai/ai-service.js.map +1 -1
- package/bin/service/ai/helpers.d.ts +8 -35
- package/bin/service/ai/helpers.d.ts.map +1 -1
- package/bin/service/ai/helpers.js +8 -140
- package/bin/service/ai/helpers.js.map +1 -1
- package/bin/service/ai/image-job-store.d.ts +2 -3
- package/bin/service/ai/image-job-store.d.ts.map +1 -1
- package/bin/service/ai/image-job-store.js.map +1 -1
- package/bin/service/ai/language-model-stream.d.ts +9 -4
- package/bin/service/ai/language-model-stream.d.ts.map +1 -1
- package/bin/service/ai/language-model-stream.js +8 -4
- package/bin/service/ai/language-model-stream.js.map +1 -1
- package/bin/service/ai/language-model-text.d.ts +15 -0
- package/bin/service/ai/language-model-text.d.ts.map +1 -0
- package/bin/service/ai/language-model-text.js +67 -0
- package/bin/service/ai/language-model-text.js.map +1 -0
- package/bin/service/ai/model-context-window.d.ts +2 -2
- package/bin/service/ai/model-context-window.d.ts.map +1 -1
- package/bin/service/ai/model-context-window.js.map +1 -1
- package/bin/service/ai/model-registry.d.ts +8 -7
- package/bin/service/ai/model-registry.d.ts.map +1 -1
- package/bin/service/ai/model-registry.js +2 -5
- package/bin/service/ai/model-registry.js.map +1 -1
- package/bin/service/ai/model-routing.d.ts +1 -1
- package/bin/service/ai/model-routing.d.ts.map +1 -1
- package/bin/service/ai/model-routing.js +1 -1
- package/bin/service/ai/model-routing.js.map +1 -1
- package/bin/service/ai/reasoning.d.ts +4 -4
- package/bin/service/ai/reasoning.d.ts.map +1 -1
- package/bin/service/ai/reasoning.js.map +1 -1
- package/bin/service/service.d.ts +8 -1
- package/bin/service/service.d.ts.map +1 -1
- package/bin/service/service.js.map +1 -1
- package/bin/types/AI.d.ts +289 -0
- package/bin/types/AI.d.ts.map +1 -0
- package/bin/types/AI.js +8 -0
- package/bin/types/AI.js.map +1 -0
- package/bin/types/AITransport.d.ts +251 -0
- package/bin/types/AITransport.d.ts.map +1 -0
- package/bin/types/AITransport.js +12 -0
- package/bin/types/AITransport.js.map +1 -0
- package/bin/types/Metering.d.ts +2 -2
- package/bin/types/Metering.d.ts.map +1 -1
- package/bin/utils/CityLanguageModelCodec.d.ts +1 -1
- package/bin/utils/CityLanguageModelCodec.d.ts.map +1 -1
- package/bin/utils/CityLanguageModelResult.d.ts +11 -0
- package/bin/utils/CityLanguageModelResult.d.ts.map +1 -0
- package/bin/utils/CityLanguageModelResult.js +76 -0
- package/bin/utils/CityLanguageModelResult.js.map +1 -0
- package/package.json +2 -2
- package/bin/service/ai/charge.d.ts +0 -76
- package/bin/service/ai/charge.d.ts.map +0 -1
- package/bin/service/ai/charge.js +0 -10
- package/bin/service/ai/charge.js.map +0 -1
- package/bin/service/ai/job-types.d.ts +0 -69
- package/bin/service/ai/job-types.d.ts.map +0 -1
- package/bin/service/ai/job-types.js +0 -9
- package/bin/service/ai/job-types.js.map +0 -1
- package/bin/service/ai/provider.d.ts +0 -141
- package/bin/service/ai/provider.d.ts.map +0 -1
- package/bin/service/ai/provider.js +0 -220
- package/bin/service/ai/provider.js.map +0 -1
- package/bin/service/ai/types.d.ts +0 -172
- package/bin/service/ai/types.d.ts.map +0 -1
- package/bin/service/ai/types.js +0 -7
- package/bin/service/ai/types.js.map +0 -1
- package/bin/types/AIImageJobClaim.d.ts +0 -14
- package/bin/types/AIImageJobClaim.d.ts.map +0 -1
- package/bin/types/AIImageJobClaim.js +0 -7
- package/bin/types/AIImageJobClaim.js.map +0 -1
- package/bin/types/AIReasoning.d.ts +0 -25
- package/bin/types/AIReasoning.d.ts.map +0 -1
- package/bin/types/AIReasoning.js +0 -7
- package/bin/types/AIReasoning.js.map +0 -1
- package/bin/types/AIRouting.d.ts +0 -37
- package/bin/types/AIRouting.d.ts.map +0 -1
- package/bin/types/AIRouting.js +0 -7
- package/bin/types/AIRouting.js.map +0 -1
- package/bin/types/CityLanguageModelRuntime.d.ts +0 -46
- package/bin/types/CityLanguageModelRuntime.d.ts.map +0 -1
- package/bin/types/CityLanguageModelRuntime.js +0 -8
- package/bin/types/CityLanguageModelRuntime.js.map +0 -1
- package/bin/types/CityLanguageModelTransport.d.ts +0 -49
- package/bin/types/CityLanguageModelTransport.d.ts.map +0 -1
- package/bin/types/CityLanguageModelTransport.js +0 -9
- package/bin/types/CityLanguageModelTransport.js.map +0 -1
- package/bin/types/CityModelRuntime.d.ts +0 -17
- package/bin/types/CityModelRuntime.d.ts.map +0 -1
- package/bin/types/CityModelRuntime.js +0 -8
- package/bin/types/CityModelRuntime.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AIService 值解析与类型守卫模块。
|
|
3
3
|
*
|
|
4
|
-
* 集中管理
|
|
4
|
+
* 集中管理 AIChannel 输出、usage 和图片任务数据的纯函数转换,让 AIService 主模块
|
|
5
5
|
* 只保留路由、生命周期与持久化编排。
|
|
6
6
|
*/
|
|
7
7
|
import { httpError } from "../../utils/helpers.js";
|
|
@@ -14,12 +14,8 @@ export function isResponse(value) {
|
|
|
14
14
|
export function isRecord(value) {
|
|
15
15
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
16
16
|
}
|
|
17
|
-
/** 判断
|
|
18
|
-
export function
|
|
19
|
-
return isRecord(value) && value.response instanceof Response;
|
|
20
|
-
}
|
|
21
|
-
/** 判断 Provider 是否返回了带账单普通输出。 */
|
|
22
|
-
export function isProviderChargedOutput(value) {
|
|
17
|
+
/** 判断 AIChannel 是否返回了带账单 Response。 */
|
|
18
|
+
export function isChannelChargedOutput(value) {
|
|
23
19
|
return isRecord(value) && "output" in value;
|
|
24
20
|
}
|
|
25
21
|
/** 判断一个值是否为 Promise-like。 */
|
|
@@ -104,7 +100,7 @@ export function imageActionError(error, fallback_message) {
|
|
|
104
100
|
return error;
|
|
105
101
|
return httpError(502, error instanceof Error ? error.message : fallback_message);
|
|
106
102
|
}
|
|
107
|
-
/**
|
|
103
|
+
/** 从输出对象中读取上游 usage。 */
|
|
108
104
|
export function extractUsage(output) {
|
|
109
105
|
if (!isRecord(output))
|
|
110
106
|
return undefined;
|
|
@@ -117,7 +113,7 @@ export function extractUsage(output) {
|
|
|
117
113
|
return output.usage;
|
|
118
114
|
return undefined;
|
|
119
115
|
}
|
|
120
|
-
/**
|
|
116
|
+
/** 兼容常见上游 usage 字段。 */
|
|
121
117
|
export function normalizeUsage(usage) {
|
|
122
118
|
return normalizeAIUsage(usage);
|
|
123
119
|
}
|
|
@@ -149,15 +145,15 @@ export function readFilePartMediaType(part) {
|
|
|
149
145
|
export function readFilePartFilename(part) {
|
|
150
146
|
return readOptionalString(part.filename);
|
|
151
147
|
}
|
|
152
|
-
/** 判断
|
|
153
|
-
export function
|
|
148
|
+
/** 判断 AIChannel 是否返回了图片任务创建结果。 */
|
|
149
|
+
export function isImageChannelCreateResult(value) {
|
|
154
150
|
if (!value || typeof value !== "object")
|
|
155
151
|
return false;
|
|
156
152
|
const record = value;
|
|
157
153
|
return typeof record.job_id === "string" && Boolean(record.job_id.trim()) && isImageJobStatus(record.status);
|
|
158
154
|
}
|
|
159
|
-
/** 判断
|
|
160
|
-
export function
|
|
155
|
+
/** 判断 AIChannel 是否返回了图片任务查询结果。 */
|
|
156
|
+
export function isImageChannelResult(value) {
|
|
161
157
|
if (!value || typeof value !== "object")
|
|
162
158
|
return false;
|
|
163
159
|
const record = value;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-service-values.js","sourceRoot":"","sources":["../../../src/service/ai/ai-service-values.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-service-values.js","sourceRoot":"","sources":["../../../src/service/ai/ai-service-values.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { Service } from "../service.js";
|
|
19
19
|
import type { ActionFn } from "../action.js";
|
|
20
|
-
import type {
|
|
20
|
+
import type { CityModelDescriptor } from "@downcity/type";
|
|
21
|
+
import type { AIModelDefinition, AIServiceOptions } from "../../types/AI.js";
|
|
21
22
|
type EnvReader = (key: string) => string | undefined;
|
|
22
23
|
export declare class AIService extends Service {
|
|
23
24
|
/** 模型注册表 */
|
|
@@ -29,20 +30,18 @@ export declare class AIService extends Service {
|
|
|
29
30
|
/** 图片异步任务允许保持 queued/running 的最长时间。 */
|
|
30
31
|
private readonly image_max_pending_duration_ms;
|
|
31
32
|
constructor(options?: AIServiceOptions);
|
|
32
|
-
use(...inputs: (
|
|
33
|
-
listModels():
|
|
33
|
+
use(...inputs: (AIModelDefinition | AIModelDefinition[])[]): this;
|
|
34
|
+
listModels(): AIModelDefinition[];
|
|
34
35
|
hasAction(): boolean;
|
|
35
36
|
resolve(query: {
|
|
36
37
|
model?: string;
|
|
37
38
|
mode?: string;
|
|
38
39
|
}, env?: EnvReader): {
|
|
39
|
-
model?:
|
|
40
|
+
model?: AIModelDefinition;
|
|
40
41
|
action: ActionFn;
|
|
41
42
|
};
|
|
42
43
|
private normalizeModelId;
|
|
43
44
|
private getAction;
|
|
44
|
-
private resolveOpenAIAction;
|
|
45
|
-
private createAutoPassthroughAction;
|
|
46
45
|
private getModelModalities;
|
|
47
46
|
/** 按媒体输入解析最终模型,推理强度必须在该步骤之后解析。 */
|
|
48
47
|
private plan_text_execution;
|
|
@@ -50,8 +49,8 @@ export declare class AIService extends Service {
|
|
|
50
49
|
/**
|
|
51
50
|
* 执行 CityModel LanguageModelV3 模型流调用。
|
|
52
51
|
*
|
|
53
|
-
* 路由、fallback、reasoning 和计费仍由 AIService 统一拥有;
|
|
54
|
-
* 标准模型流,transport 模块只编码 SSE,避免把
|
|
52
|
+
* 路由、fallback、reasoning 和计费仍由 AIService 统一拥有;AIChannel 负责执行
|
|
53
|
+
* 标准模型流,transport 模块只编码 SSE,避免把 Channel 决策泄漏到客户端。
|
|
55
54
|
*/
|
|
56
55
|
private handleLanguageModelStream;
|
|
57
56
|
private createImageJob;
|
|
@@ -94,7 +93,7 @@ export declare class AIService extends Service {
|
|
|
94
93
|
*/
|
|
95
94
|
private requireImageJob;
|
|
96
95
|
/**
|
|
97
|
-
* 把 async_jobs 记录注入
|
|
96
|
+
* 把 async_jobs 记录注入 AIChannel 可读取的上下文。
|
|
98
97
|
*/
|
|
99
98
|
private attachImageJobContext;
|
|
100
99
|
/**
|
|
@@ -119,14 +118,16 @@ export declare class AIService extends Service {
|
|
|
119
118
|
* 从 action 输出里提取标准计量信息。
|
|
120
119
|
*/
|
|
121
120
|
private attachOutputMetering;
|
|
121
|
+
/** 构造账单函数允许访问的显式领域输入。 */
|
|
122
|
+
private build_bill_input;
|
|
122
123
|
/**
|
|
123
|
-
* 拆包
|
|
124
|
+
* 拆包 AIChannel 返回值。
|
|
124
125
|
*
|
|
125
126
|
* 关键说明(中文)
|
|
126
|
-
* -
|
|
127
|
-
* -
|
|
127
|
+
* - Channel 可以返回统一的 `{ output, charge }`。
|
|
128
|
+
* - 普通 action 也可以直接返回 UIMessage / Response。
|
|
128
129
|
*/
|
|
129
|
-
private
|
|
130
|
+
private resolveChannelOutput;
|
|
130
131
|
/**
|
|
131
132
|
* 安排 AI 专用扣费。
|
|
132
133
|
*/
|
|
@@ -134,7 +135,7 @@ export declare class AIService extends Service {
|
|
|
134
135
|
static listModels(aiService: AIService, options: {
|
|
135
136
|
env: EnvReader;
|
|
136
137
|
identity: "guest" | "user" | "admin";
|
|
137
|
-
}):
|
|
138
|
+
}): CityModelDescriptor[];
|
|
138
139
|
}
|
|
139
140
|
export {};
|
|
140
141
|
//# sourceMappingURL=ai-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-service.d.ts","sourceRoot":"","sources":["../../../src/service/ai/ai-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAgB,MAAM,eAAe,CAAC;AAEtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ai-service.d.ts","sourceRoot":"","sources":["../../../src/service/ai/ai-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAgB,MAAM,eAAe,CAAC;AAEtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAQV,iBAAiB,EAIjB,gBAAgB,EAGjB,MAAM,mBAAmB,CAAC;AAqE3B,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAWrD,qBAAa,SAAU,SAAQ,OAAO;IACpC,YAAY;IACZ,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAEhD,0CAA0C;IAC1C,OAAO,CAAC,eAAe,CAA+B;IAEtD,iBAAiB;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkB;IAC3C,uCAAuC;IACvC,OAAO,CAAC,QAAQ,CAAC,6BAA6B,CAAS;gBAE3C,OAAO,GAAE,gBAAqB;IA+C1C,GAAG,CAAC,GAAG,MAAM,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,EAAE,CAAC,EAAE,GAAG,IAAI;IAKjE,UAAU,IAAI,iBAAiB,EAAE;IAIjC,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,iBAAiB,CAAC;QAAC,MAAM,EAAE,QAAQ,CAAA;KAAE;IAenH,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,SAAS;IAmBjB,OAAO,CAAC,kBAAkB;IAU1B,kCAAkC;IAClC,OAAO,CAAC,mBAAmB;YAcb,cAAc;IAmC5B;;;;;OAKG;YACW,yBAAyB;YA8CzB,cAAc;YAgBd,YAAY;IAS1B;;OAEG;YACW,aAAa;IAuD3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAI1B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAOjC;;OAEG;IACH,OAAO,CAAC,kCAAkC;IAc1C;;OAEG;YACW,iBAAiB;IAU/B;;;;;;;OAOG;YACW,2BAA2B;IAwDzC;;OAEG;YACW,cAAc;IAuB5B;;OAEG;YACW,eAAe;IAW7B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;OAEG;IACH,OAAO,CAAC,gBAAgB;YAcV,qBAAqB;IAsDnC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA8B3B;;;;;;;OAOG;YACW,QAAQ;IAMtB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAgB5B,yBAAyB;IACzB,OAAO,CAAC,gBAAgB;IAiBxB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAU5B;;OAEG;YACW,YAAY;IA0D1B,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,mBAAmB,EAAE;CAM1B"}
|
|
@@ -24,7 +24,8 @@ import { resolve_text_routing_plan } from "./model-routing.js";
|
|
|
24
24
|
import { claim_image_job, finish_image_job_fetch, release_image_job_claim, } from "./image-job-store.js";
|
|
25
25
|
import { settle_response_charge } from "./charge-runtime.js";
|
|
26
26
|
import { create_city_language_model_stream, decode_city_language_model_request, prepare_city_language_model_call, } from "./language-model-stream.js";
|
|
27
|
-
import {
|
|
27
|
+
import { create_openai_chat_completion_response, openai_chat_request_to_language_model_call, } from "./OpenAIChatCompletionsAdapter.js";
|
|
28
|
+
import { countImageOutputs, extractUsage, imageActionError, isImageChannelCreateResult, isImageChannelResult, isPromiseLike, isChannelChargedOutput, isResponse, isStorableRemoteFilePart, normalizePositiveNumber, normalizeUsage, parseImageMessage, parseRecordJson, readFilePartFilename, readFilePartMediaType, readOptionalNumber, readOptionalString, rowToAsyncJobRecord, } from "./ai-service-values.js";
|
|
28
29
|
/** AIService 直接暴露的 action 模态列表。模型流与图片任务使用独立 handler。 */
|
|
29
30
|
const MODALITIES = ["text", "video", "tts", "asr"];
|
|
30
31
|
/** 用户侧默认以 text 模态排序模型 */
|
|
@@ -41,7 +42,7 @@ const IMAGE_FETCH_ACTION = "image/fetch";
|
|
|
41
42
|
const DEFAULT_IMAGE_MAX_PENDING_DURATION_MS = 2 * 60 * 60 * 1000;
|
|
42
43
|
/** 图片任务 pending 超时错误。 */
|
|
43
44
|
const IMAGE_PENDING_TIMEOUT_ERROR = "upstream timeout";
|
|
44
|
-
/** 判断
|
|
45
|
+
/** 判断 AIChannel runtime 是否返回了标准模型流结果。 */
|
|
45
46
|
function is_language_model_stream_result(value) {
|
|
46
47
|
if (!value || typeof value !== "object")
|
|
47
48
|
return false;
|
|
@@ -108,7 +109,6 @@ export class AIService extends Service {
|
|
|
108
109
|
// ========== 模型匹配 ==========
|
|
109
110
|
resolve(query, env) {
|
|
110
111
|
const { model: modelId, mode } = query;
|
|
111
|
-
const isOpenAIMode = mode === "openai";
|
|
112
112
|
if (!modelId)
|
|
113
113
|
throw httpError(422, "model is required");
|
|
114
114
|
const model = this.models.get(modelId);
|
|
@@ -117,7 +117,7 @@ export class AIService extends Service {
|
|
|
117
117
|
if (env && this.models.get_missing_env(model, env).length > 0) {
|
|
118
118
|
throw httpError(422, `No available model: ${modelId}`);
|
|
119
119
|
}
|
|
120
|
-
const action =
|
|
120
|
+
const action = this.getAction(model, mode);
|
|
121
121
|
if (!action)
|
|
122
122
|
throw httpError(422, `Model ${modelId} does not support mode: ${mode ?? "text"}`);
|
|
123
123
|
return { model, action };
|
|
@@ -128,77 +128,35 @@ export class AIService extends Service {
|
|
|
128
128
|
}
|
|
129
129
|
getAction(model, mode) {
|
|
130
130
|
if (mode === LANGUAGE_MODEL_MODE) {
|
|
131
|
-
return model.
|
|
132
|
-
? (ctx) => model.
|
|
131
|
+
return model.runtime.stream
|
|
132
|
+
? (ctx) => model.runtime.stream?.(ctx, ctx.input.call)
|
|
133
133
|
: undefined;
|
|
134
134
|
}
|
|
135
135
|
if (mode === "image" || IMAGE_ACTION_MODES.includes(mode)) {
|
|
136
|
-
const has_image_actions = Boolean(model.actions.image_create && model.actions.image_fetch);
|
|
136
|
+
const has_image_actions = Boolean(model.runtime.actions.image_create && model.runtime.actions.image_fetch);
|
|
137
137
|
if (!has_image_actions)
|
|
138
138
|
return undefined;
|
|
139
139
|
return mode === "image"
|
|
140
|
-
? model.actions.image_create
|
|
141
|
-
: model.actions[mode];
|
|
140
|
+
? model.runtime.actions.image_create
|
|
141
|
+
: model.runtime.actions[mode];
|
|
142
142
|
}
|
|
143
|
-
return model.actions[(mode ?? "text")];
|
|
144
|
-
}
|
|
145
|
-
resolveOpenAIAction(model) {
|
|
146
|
-
if (model.actions.openai)
|
|
147
|
-
return model.actions.openai;
|
|
148
|
-
if (model.baseURL && model.envKey)
|
|
149
|
-
return this.createAutoPassthroughAction(model);
|
|
150
|
-
return undefined;
|
|
151
|
-
}
|
|
152
|
-
createAutoPassthroughAction(model) {
|
|
153
|
-
const baseURL = model.baseURL;
|
|
154
|
-
const envKey = model.envKey;
|
|
155
|
-
const passthroughModel = model.passthroughModel;
|
|
156
|
-
return async (ctx) => {
|
|
157
|
-
const apiKey = ctx.env(envKey);
|
|
158
|
-
if (!apiKey) {
|
|
159
|
-
return new Response(JSON.stringify({
|
|
160
|
-
error: { message: `${envKey} is required`, type: "authentication_error" },
|
|
161
|
-
}), { status: 401, headers: { "content-type": "application/json" } });
|
|
162
|
-
}
|
|
163
|
-
const body = { ...ctx.input };
|
|
164
|
-
if (passthroughModel) {
|
|
165
|
-
body.model = passthroughModel;
|
|
166
|
-
}
|
|
167
|
-
const response = await fetch(`${baseURL}/chat/completions`, {
|
|
168
|
-
method: "POST",
|
|
169
|
-
headers: {
|
|
170
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
171
|
-
"Content-Type": "application/json",
|
|
172
|
-
},
|
|
173
|
-
body: JSON.stringify(body),
|
|
174
|
-
});
|
|
175
|
-
return new Response(response.body, {
|
|
176
|
-
status: response.status,
|
|
177
|
-
statusText: response.statusText,
|
|
178
|
-
headers: response.headers,
|
|
179
|
-
});
|
|
180
|
-
};
|
|
143
|
+
return model.runtime.actions[(mode ?? "text")];
|
|
181
144
|
}
|
|
182
145
|
getModelModalities(model) {
|
|
183
|
-
const modalities = Object.keys(model.
|
|
184
|
-
|
|
146
|
+
const modalities = Object.keys(model.runtime.actions)
|
|
147
|
+
.filter((key) => model.runtime.actions[key] !== undefined);
|
|
148
|
+
if (model.runtime.stream && !modalities.includes("stream"))
|
|
185
149
|
modalities.push("stream");
|
|
186
150
|
if (modalities.includes("image_create") && modalities.includes("image_fetch")) {
|
|
187
151
|
modalities.push("image");
|
|
188
152
|
}
|
|
189
|
-
if (!modalities.includes("openai") && this.resolveOpenAIAction(model))
|
|
190
|
-
modalities.push("openai");
|
|
191
153
|
return modalities.filter((mode) => mode !== "image_create" && mode !== "image_fetch" && mode !== "image_result");
|
|
192
154
|
}
|
|
193
155
|
/** 按媒体输入解析最终模型,推理强度必须在该步骤之后解析。 */
|
|
194
156
|
plan_text_execution(resolved, ctx, mode) {
|
|
195
157
|
return resolve_text_routing_plan(resolved, ctx.input, mode, {
|
|
196
|
-
resolve_model: (input) =>
|
|
197
|
-
|
|
198
|
-
: this.models.get(input.id) ?? input,
|
|
199
|
-
resolve_action: (model, target_mode) => target_mode === "openai"
|
|
200
|
-
? this.resolveOpenAIAction(model)
|
|
201
|
-
: this.getAction(model, target_mode),
|
|
158
|
+
resolve_model: (input) => this.models.get(input),
|
|
159
|
+
resolve_action: (model, target_mode) => this.getAction(model, target_mode),
|
|
202
160
|
is_available: (model) => this.models.get_missing_env(model, ctx.env).length === 0,
|
|
203
161
|
});
|
|
204
162
|
}
|
|
@@ -213,10 +171,12 @@ export class AIService extends Service {
|
|
|
213
171
|
attach_resolved_reasoning(ctx, reasoning);
|
|
214
172
|
const started_at = Date.now();
|
|
215
173
|
try {
|
|
216
|
-
const
|
|
217
|
-
const { output, charge } = this.
|
|
174
|
+
const channel_output = await resolved.action(ctx);
|
|
175
|
+
const { output, charge } = this.resolveChannelOutput(channel_output);
|
|
218
176
|
this.attachOutputMetering(ctx, output, modality, started_at);
|
|
219
|
-
const resolved_charge = charge ?? resolved.model
|
|
177
|
+
const resolved_charge = charge ?? (resolved.model
|
|
178
|
+
? resolved.model.bill?.(this.build_bill_input(ctx, resolved.model, output))
|
|
179
|
+
: undefined);
|
|
220
180
|
const defer_charge = isResponse(output) || isPromiseLike(resolved_charge);
|
|
221
181
|
const charged_response = await this.handleCharge(ctx, resolved_charge, defer_charge, undefined, undefined, isResponse(output) ? output : undefined);
|
|
222
182
|
if (isResponse(output))
|
|
@@ -232,8 +192,8 @@ export class AIService extends Service {
|
|
|
232
192
|
/**
|
|
233
193
|
* 执行 CityModel LanguageModelV3 模型流调用。
|
|
234
194
|
*
|
|
235
|
-
* 路由、fallback、reasoning 和计费仍由 AIService 统一拥有;
|
|
236
|
-
* 标准模型流,transport 模块只编码 SSE,避免把
|
|
195
|
+
* 路由、fallback、reasoning 和计费仍由 AIService 统一拥有;AIChannel 负责执行
|
|
196
|
+
* 标准模型流,transport 模块只编码 SSE,避免把 Channel 决策泄漏到客户端。
|
|
237
197
|
*/
|
|
238
198
|
async handleLanguageModelStream(ctx) {
|
|
239
199
|
const request = decode_city_language_model_request(ctx.input);
|
|
@@ -253,7 +213,7 @@ export class AIService extends Service {
|
|
|
253
213
|
const started_at = Date.now();
|
|
254
214
|
const output = await resolved.action(ctx);
|
|
255
215
|
if (!is_language_model_stream_result(output)) {
|
|
256
|
-
throw httpError(500, "
|
|
216
|
+
throw httpError(500, "AIChannel stream did not return a LanguageModelV3 stream result");
|
|
257
217
|
}
|
|
258
218
|
const execution = create_city_language_model_stream({
|
|
259
219
|
result: output,
|
|
@@ -264,7 +224,9 @@ export class AIService extends Service {
|
|
|
264
224
|
return part;
|
|
265
225
|
});
|
|
266
226
|
const charge = resolved.model?.bill
|
|
267
|
-
? completion.then((part) => part
|
|
227
|
+
? completion.then((part) => part && resolved.model
|
|
228
|
+
? resolved.model.bill?.(this.build_bill_input(ctx, resolved.model, part))
|
|
229
|
+
: undefined)
|
|
268
230
|
: undefined;
|
|
269
231
|
const charged_response = await this.handleCharge(ctx, charge, true, undefined, undefined, execution.response);
|
|
270
232
|
return charged_response ?? execution.response;
|
|
@@ -275,7 +237,7 @@ export class AIService extends Service {
|
|
|
275
237
|
this.attachResolvedModel(ctx, resolved.model, "image/create");
|
|
276
238
|
try {
|
|
277
239
|
const created = await resolved.action(ctx);
|
|
278
|
-
if (!
|
|
240
|
+
if (!isImageChannelCreateResult(created)) {
|
|
279
241
|
throw httpError(500, "image_create action returned invalid result");
|
|
280
242
|
}
|
|
281
243
|
await this.insertImageJob(ctx, created);
|
|
@@ -320,21 +282,21 @@ export class AIService extends Service {
|
|
|
320
282
|
if (!model_id)
|
|
321
283
|
throw httpError(422, "Image job is missing model_id");
|
|
322
284
|
const model = this.models.get(model_id);
|
|
323
|
-
if (!model?.actions.image_fetch) {
|
|
285
|
+
if (!model?.runtime.actions.image_fetch) {
|
|
324
286
|
throw httpError(422, `No image_fetch action for model: ${model_id}`);
|
|
325
287
|
}
|
|
326
288
|
this.attachResolvedModel(ctx, model, IMAGE_FETCH_ACTION);
|
|
327
289
|
this.attachImageJobContext(ctx, job);
|
|
328
290
|
const started_at = Date.now();
|
|
329
|
-
const output = await model.actions.image_fetch(ctx);
|
|
330
|
-
if (!
|
|
291
|
+
const output = await model.runtime.actions.image_fetch(ctx);
|
|
292
|
+
if (!isImageChannelResult(output)) {
|
|
331
293
|
throw httpError(500, "image_fetch action returned invalid result");
|
|
332
294
|
}
|
|
333
295
|
const stored_output = await this.normalizeImageResultStorage(ctx, output);
|
|
334
296
|
const should_charge = output.status === "succeeded" && Boolean(output.result) && !job.result_json;
|
|
335
297
|
if (should_charge) {
|
|
336
298
|
this.attachOutputMetering(ctx, stored_output.result, "image", started_at);
|
|
337
|
-
const charge = model.bill?.(ctx, stored_output);
|
|
299
|
+
const charge = model.bill?.(this.build_bill_input(ctx, model, stored_output));
|
|
338
300
|
await this.handleCharge(ctx, charge, false, `ai_image:${job.job_id}`, job.user_id ?? undefined);
|
|
339
301
|
}
|
|
340
302
|
await finish_image_job_fetch(table, claim, stored_output);
|
|
@@ -497,7 +459,7 @@ export class AIService extends Service {
|
|
|
497
459
|
return rowToAsyncJobRecord(row);
|
|
498
460
|
}
|
|
499
461
|
/**
|
|
500
|
-
* 把 async_jobs 记录注入
|
|
462
|
+
* 把 async_jobs 记录注入 AIChannel 可读取的上下文。
|
|
501
463
|
*/
|
|
502
464
|
attachImageJobContext(ctx, job) {
|
|
503
465
|
const image_job = {
|
|
@@ -528,27 +490,45 @@ export class AIService extends Service {
|
|
|
528
490
|
}
|
|
529
491
|
// ========== OpenAI 兼容通路 ==========
|
|
530
492
|
async handleChatCompletions(ctx) {
|
|
531
|
-
const body = ctx.input;
|
|
532
|
-
const modelId = this.normalizeModelId(body.model);
|
|
533
|
-
const initial_resolved = this.resolve({ model: modelId, mode: "openai" }, ctx.env);
|
|
534
|
-
const { resolved, fallback_from, fallback_reason, fallback_media_type } = this.plan_text_execution(initial_resolved, ctx, "openai");
|
|
535
|
-
const reasoning = resolved.model
|
|
536
|
-
? resolve_model_reasoning(resolved.model, body)
|
|
537
|
-
: undefined;
|
|
538
|
-
this.attachResolvedModel(ctx, resolved.model, "openai", { fallback_from, fallback_reason, fallback_media_type });
|
|
539
|
-
ctx.input = body;
|
|
540
|
-
attach_resolved_reasoning(ctx, reasoning);
|
|
541
|
-
const started_at = Date.now();
|
|
542
493
|
try {
|
|
494
|
+
const body = ctx.input;
|
|
495
|
+
const model_id = this.normalizeModelId(body.model);
|
|
496
|
+
const call = openai_chat_request_to_language_model_call(body, ctx.request?.signal);
|
|
497
|
+
ctx.input = {
|
|
498
|
+
...body,
|
|
499
|
+
model: model_id,
|
|
500
|
+
call,
|
|
501
|
+
};
|
|
502
|
+
const initial_resolved = this.resolve({ model: model_id, mode: LANGUAGE_MODEL_MODE }, ctx.env);
|
|
503
|
+
const routing = this.plan_text_execution(initial_resolved, ctx, LANGUAGE_MODEL_MODE);
|
|
504
|
+
const resolved = routing.resolved;
|
|
505
|
+
const reasoning = resolved.model
|
|
506
|
+
? resolve_model_reasoning(resolved.model, body)
|
|
507
|
+
: undefined;
|
|
508
|
+
this.attachResolvedModel(ctx, resolved.model, "openai", routing);
|
|
509
|
+
attach_resolved_reasoning(ctx, reasoning);
|
|
510
|
+
const started_at = Date.now();
|
|
543
511
|
const output = await resolved.action(ctx);
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
512
|
+
if (!is_language_model_stream_result(output)) {
|
|
513
|
+
throw httpError(500, "AIChannel stream did not return a LanguageModelV3 stream result");
|
|
514
|
+
}
|
|
515
|
+
const execution = await create_openai_chat_completion_response({
|
|
516
|
+
model_id: resolved.model?.id ?? model_id ?? "",
|
|
517
|
+
stream: body.stream === true,
|
|
518
|
+
result: output,
|
|
519
|
+
});
|
|
520
|
+
const completion = execution.completion.then((result) => {
|
|
521
|
+
if (result)
|
|
522
|
+
this.attachOutputMetering(ctx, result, "openai", started_at);
|
|
523
|
+
return result;
|
|
524
|
+
});
|
|
525
|
+
const charge = resolved.model?.bill
|
|
526
|
+
? completion.then((result) => result && resolved.model
|
|
527
|
+
? resolved.model.bill?.(this.build_bill_input(ctx, resolved.model, result))
|
|
528
|
+
: undefined)
|
|
529
|
+
: undefined;
|
|
530
|
+
const charged_response = await this.handleCharge(ctx, charge, true, undefined, undefined, execution.response);
|
|
531
|
+
return charged_response ?? execution.response;
|
|
552
532
|
}
|
|
553
533
|
catch (error) {
|
|
554
534
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -562,14 +542,18 @@ export class AIService extends Service {
|
|
|
562
542
|
attachResolvedModel(ctx, model, mode, routing) {
|
|
563
543
|
if (!model)
|
|
564
544
|
return;
|
|
565
|
-
ctx.variant = {
|
|
545
|
+
ctx.variant = {
|
|
546
|
+
id: model.id,
|
|
547
|
+
name: model.name,
|
|
548
|
+
meta: model.meta,
|
|
549
|
+
upstream_model: model.upstream_model,
|
|
550
|
+
channel_id: model.channel_id,
|
|
551
|
+
};
|
|
566
552
|
ctx.metering = {
|
|
567
553
|
...ctx.metering,
|
|
568
|
-
|
|
554
|
+
channel_id: model.channel_id,
|
|
569
555
|
model_id: model.id,
|
|
570
|
-
upstream_model:
|
|
571
|
-
? model.meta.upstream_model
|
|
572
|
-
: model.passthroughModel,
|
|
556
|
+
upstream_model: model.upstream_model,
|
|
573
557
|
request_count: ctx.metering?.request_count ?? 1,
|
|
574
558
|
metadata: {
|
|
575
559
|
...(ctx.metering?.metadata ?? {}),
|
|
@@ -611,21 +595,28 @@ export class AIService extends Service {
|
|
|
611
595
|
raw_usage: usage ?? ctx.metering?.raw_usage,
|
|
612
596
|
};
|
|
613
597
|
}
|
|
598
|
+
/** 构造账单函数允许访问的显式领域输入。 */
|
|
599
|
+
build_bill_input(ctx, model, output) {
|
|
600
|
+
return {
|
|
601
|
+
output,
|
|
602
|
+
model: {
|
|
603
|
+
id: model.id,
|
|
604
|
+
upstream_model: model.upstream_model,
|
|
605
|
+
},
|
|
606
|
+
...(ctx.metering ? { metering: ctx.metering } : {}),
|
|
607
|
+
...(ctx.user?.user_id ? { user_id: ctx.user.user_id } : {}),
|
|
608
|
+
...(ctx.city?.city_id ? { city_id: ctx.city.city_id } : {}),
|
|
609
|
+
};
|
|
610
|
+
}
|
|
614
611
|
/**
|
|
615
|
-
* 拆包
|
|
612
|
+
* 拆包 AIChannel 返回值。
|
|
616
613
|
*
|
|
617
614
|
* 关键说明(中文)
|
|
618
|
-
* -
|
|
619
|
-
* -
|
|
615
|
+
* - Channel 可以返回统一的 `{ output, charge }`。
|
|
616
|
+
* - 普通 action 也可以直接返回 UIMessage / Response。
|
|
620
617
|
*/
|
|
621
|
-
|
|
622
|
-
if (
|
|
623
|
-
return {
|
|
624
|
-
output: value.response,
|
|
625
|
-
charge: value.charge,
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
if (isProviderChargedOutput(value)) {
|
|
618
|
+
resolveChannelOutput(value) {
|
|
619
|
+
if (isChannelChargedOutput(value)) {
|
|
629
620
|
return {
|
|
630
621
|
output: value.output,
|
|
631
622
|
charge: value.charge,
|
|
@@ -664,7 +655,7 @@ export class AIService extends Service {
|
|
|
664
655
|
service_id: ctx.service?.id,
|
|
665
656
|
action_id: ctx.action?.id,
|
|
666
657
|
model_id: ctx.metering?.model_id,
|
|
667
|
-
|
|
658
|
+
channel_id: ctx.metering?.channel_id,
|
|
668
659
|
credits: charge_line?.credits,
|
|
669
660
|
error: error instanceof Error ? error.message : String(error),
|
|
670
661
|
});
|