@downcity/city 0.2.200 → 0.2.211

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 (221) hide show
  1. package/README.md +89 -13
  2. package/bin/client/bureau.d.ts +41 -0
  3. package/bin/client/bureau.d.ts.map +1 -0
  4. package/bin/client/bureau.js +240 -0
  5. package/bin/client/bureau.js.map +1 -0
  6. package/bin/client/city.d.ts +15 -79
  7. package/bin/client/city.d.ts.map +1 -1
  8. package/bin/client/city.js +15 -99
  9. package/bin/client/city.js.map +1 -1
  10. package/bin/client/federation-admin.d.ts +32 -0
  11. package/bin/client/federation-admin.d.ts.map +1 -0
  12. package/bin/client/federation-admin.js +54 -0
  13. package/bin/client/federation-admin.js.map +1 -0
  14. package/bin/client/types.d.ts +10 -70
  15. package/bin/client/types.d.ts.map +1 -1
  16. package/bin/client/types.js +3 -3
  17. package/bin/federation/auth/authenticator.d.ts +17 -13
  18. package/bin/federation/auth/authenticator.d.ts.map +1 -1
  19. package/bin/federation/auth/authenticator.js +38 -27
  20. package/bin/federation/auth/authenticator.js.map +1 -1
  21. package/bin/federation/auth/bureau-token-schema.d.ts +274 -0
  22. package/bin/federation/auth/bureau-token-schema.d.ts.map +1 -0
  23. package/bin/federation/auth/bureau-token-schema.js +43 -0
  24. package/bin/federation/auth/bureau-token-schema.js.map +1 -0
  25. package/bin/federation/auth/bureau-token-store.d.ts +22 -0
  26. package/bin/federation/auth/bureau-token-store.d.ts.map +1 -0
  27. package/bin/federation/auth/bureau-token-store.js +122 -0
  28. package/bin/federation/auth/bureau-token-store.js.map +1 -0
  29. package/bin/federation/auth/federation-key-store.d.ts +26 -0
  30. package/bin/federation/auth/federation-key-store.d.ts.map +1 -0
  31. package/bin/federation/auth/federation-key-store.js +96 -0
  32. package/bin/federation/auth/federation-key-store.js.map +1 -0
  33. package/bin/federation/auth/key-schema.d.ts +275 -0
  34. package/bin/federation/auth/key-schema.d.ts.map +1 -0
  35. package/bin/federation/auth/key-schema.js +44 -0
  36. package/bin/federation/auth/key-schema.js.map +1 -0
  37. package/bin/federation/auth/types.d.ts +62 -4
  38. package/bin/federation/auth/types.d.ts.map +1 -1
  39. package/bin/federation/auth/user-token-authority.d.ts +26 -0
  40. package/bin/federation/auth/user-token-authority.d.ts.map +1 -0
  41. package/bin/federation/auth/user-token-authority.js +179 -0
  42. package/bin/federation/auth/user-token-authority.js.map +1 -0
  43. package/bin/federation/federation-init.d.ts.map +1 -1
  44. package/bin/federation/federation-init.js +23 -4
  45. package/bin/federation/federation-init.js.map +1 -1
  46. package/bin/federation/federation-router.d.ts +3 -1
  47. package/bin/federation/federation-router.d.ts.map +1 -1
  48. package/bin/federation/federation-router.js +17 -0
  49. package/bin/federation/federation-router.js.map +1 -1
  50. package/bin/federation/federation-runtime.d.ts.map +1 -1
  51. package/bin/federation/federation-runtime.js +14 -2
  52. package/bin/federation/federation-runtime.js.map +1 -1
  53. package/bin/federation/federation.d.ts.map +1 -1
  54. package/bin/federation/federation.js +2 -0
  55. package/bin/federation/federation.js.map +1 -1
  56. package/bin/federation/runtime.d.ts +4 -0
  57. package/bin/federation/runtime.d.ts.map +1 -1
  58. package/bin/index.d.ts +11 -10
  59. package/bin/index.d.ts.map +1 -1
  60. package/bin/index.js +5 -3
  61. package/bin/index.js.map +1 -1
  62. package/bin/pact/admin/index.d.ts +7 -19
  63. package/bin/pact/admin/index.d.ts.map +1 -1
  64. package/bin/pact/admin/index.js +10 -18
  65. package/bin/pact/admin/index.js.map +1 -1
  66. package/bin/pact/admin/types.d.ts +4 -10
  67. package/bin/pact/admin/types.d.ts.map +1 -1
  68. package/bin/pact/invoker/ai/CityModel.d.ts +1 -1
  69. package/bin/pact/invoker/ai/CityModel.d.ts.map +1 -1
  70. package/bin/pact/invoker/ai/CityModel.js +3 -65
  71. package/bin/pact/invoker/ai/CityModel.js.map +1 -1
  72. package/bin/pact/invoker/ai/types.d.ts +1 -1
  73. package/bin/pact/invoker/ai/types.js +1 -1
  74. package/bin/pact/invoker/bureaus/index.d.ts +25 -0
  75. package/bin/pact/invoker/bureaus/index.d.ts.map +1 -0
  76. package/bin/pact/invoker/bureaus/index.js +36 -0
  77. package/bin/pact/invoker/bureaus/index.js.map +1 -0
  78. package/bin/pact/invoker/cities/types.d.ts +2 -2
  79. package/bin/pact/invoker/cities/types.d.ts.map +1 -1
  80. package/bin/pact/invoker/user/index.d.ts +19 -0
  81. package/bin/pact/invoker/user/index.d.ts.map +1 -0
  82. package/bin/pact/invoker/user/index.js +24 -0
  83. package/bin/pact/invoker/user/index.js.map +1 -0
  84. package/bin/pact/user/index.d.ts +2 -1
  85. package/bin/pact/user/index.d.ts.map +1 -1
  86. package/bin/pact/user/index.js +7 -3
  87. package/bin/pact/user/index.js.map +1 -1
  88. package/bin/pact/user/types.d.ts +11 -6
  89. package/bin/pact/user/types.d.ts.map +1 -1
  90. package/bin/service/ai/AIChannel.d.ts +57 -0
  91. package/bin/service/ai/AIChannel.d.ts.map +1 -0
  92. package/bin/service/ai/AIChannel.js +151 -0
  93. package/bin/service/ai/AIChannel.js.map +1 -0
  94. package/bin/service/ai/OpenAIChatCompletionsAdapter.d.ts +23 -0
  95. package/bin/service/ai/OpenAIChatCompletionsAdapter.d.ts.map +1 -0
  96. package/bin/service/ai/OpenAIChatCompletionsAdapter.js +503 -0
  97. package/bin/service/ai/OpenAIChatCompletionsAdapter.js.map +1 -0
  98. package/bin/service/ai/ai-service-values.d.ts +16 -19
  99. package/bin/service/ai/ai-service-values.d.ts.map +1 -1
  100. package/bin/service/ai/ai-service-values.js +9 -13
  101. package/bin/service/ai/ai-service-values.js.map +1 -1
  102. package/bin/service/ai/ai-service.d.ts +16 -15
  103. package/bin/service/ai/ai-service.d.ts.map +1 -1
  104. package/bin/service/ai/ai-service.js +97 -106
  105. package/bin/service/ai/ai-service.js.map +1 -1
  106. package/bin/service/ai/helpers.d.ts +8 -35
  107. package/bin/service/ai/helpers.d.ts.map +1 -1
  108. package/bin/service/ai/helpers.js +8 -140
  109. package/bin/service/ai/helpers.js.map +1 -1
  110. package/bin/service/ai/image-job-store.d.ts +2 -3
  111. package/bin/service/ai/image-job-store.d.ts.map +1 -1
  112. package/bin/service/ai/image-job-store.js.map +1 -1
  113. package/bin/service/ai/language-model-stream.d.ts +9 -4
  114. package/bin/service/ai/language-model-stream.d.ts.map +1 -1
  115. package/bin/service/ai/language-model-stream.js +8 -4
  116. package/bin/service/ai/language-model-stream.js.map +1 -1
  117. package/bin/service/ai/language-model-text.d.ts +15 -0
  118. package/bin/service/ai/language-model-text.d.ts.map +1 -0
  119. package/bin/service/ai/language-model-text.js +67 -0
  120. package/bin/service/ai/language-model-text.js.map +1 -0
  121. package/bin/service/ai/model-context-window.d.ts +2 -2
  122. package/bin/service/ai/model-context-window.d.ts.map +1 -1
  123. package/bin/service/ai/model-context-window.js.map +1 -1
  124. package/bin/service/ai/model-registry.d.ts +9 -8
  125. package/bin/service/ai/model-registry.d.ts.map +1 -1
  126. package/bin/service/ai/model-registry.js +1 -5
  127. package/bin/service/ai/model-registry.js.map +1 -1
  128. package/bin/service/ai/model-routing.d.ts +1 -1
  129. package/bin/service/ai/model-routing.d.ts.map +1 -1
  130. package/bin/service/ai/model-routing.js +1 -1
  131. package/bin/service/ai/model-routing.js.map +1 -1
  132. package/bin/service/ai/reasoning.d.ts +4 -4
  133. package/bin/service/ai/reasoning.d.ts.map +1 -1
  134. package/bin/service/ai/reasoning.js.map +1 -1
  135. package/bin/service/bureaus/bureaus-service.d.ts +15 -0
  136. package/bin/service/bureaus/bureaus-service.d.ts.map +1 -0
  137. package/bin/service/bureaus/bureaus-service.js +55 -0
  138. package/bin/service/bureaus/bureaus-service.js.map +1 -0
  139. package/bin/service/installable-service.d.ts +5 -2
  140. package/bin/service/installable-service.d.ts.map +1 -1
  141. package/bin/service/installable-service.js +1 -0
  142. package/bin/service/installable-service.js.map +1 -1
  143. package/bin/service/instruction.js +1 -1
  144. package/bin/service/instruction.js.map +1 -1
  145. package/bin/service/service.d.ts +15 -2
  146. package/bin/service/service.d.ts.map +1 -1
  147. package/bin/service/service.js +2 -0
  148. package/bin/service/service.js.map +1 -1
  149. package/bin/types/AI.d.ts +289 -0
  150. package/bin/types/AI.d.ts.map +1 -0
  151. package/bin/types/AI.js +8 -0
  152. package/bin/types/AI.js.map +1 -0
  153. package/bin/types/AITransport.d.ts +251 -0
  154. package/bin/types/AITransport.d.ts.map +1 -0
  155. package/bin/types/AITransport.js +12 -0
  156. package/bin/types/AITransport.js.map +1 -0
  157. package/bin/types/Bureau.d.ts +96 -0
  158. package/bin/types/Bureau.d.ts.map +1 -0
  159. package/bin/types/Bureau.js +8 -0
  160. package/bin/types/Bureau.js.map +1 -0
  161. package/bin/types/FederationAdmin.d.ts +16 -0
  162. package/bin/types/FederationAdmin.d.ts.map +1 -0
  163. package/bin/types/FederationAdmin.js +7 -0
  164. package/bin/types/FederationAdmin.js.map +1 -0
  165. package/bin/types/Metering.d.ts +2 -2
  166. package/bin/types/Metering.d.ts.map +1 -1
  167. package/bin/types/User.d.ts +24 -0
  168. package/bin/types/User.d.ts.map +1 -0
  169. package/bin/types/User.js +8 -0
  170. package/bin/types/User.js.map +1 -0
  171. package/bin/utils/CityLanguageModelCodec.d.ts +1 -1
  172. package/bin/utils/CityLanguageModelCodec.d.ts.map +1 -1
  173. package/bin/utils/CityLanguageModelResult.d.ts +11 -0
  174. package/bin/utils/CityLanguageModelResult.d.ts.map +1 -0
  175. package/bin/utils/CityLanguageModelResult.js +76 -0
  176. package/bin/utils/CityLanguageModelResult.js.map +1 -0
  177. package/package.json +4 -3
  178. package/bin/federation/auth/token-signer.d.ts +0 -27
  179. package/bin/federation/auth/token-signer.d.ts.map +0 -1
  180. package/bin/federation/auth/token-signer.js +0 -116
  181. package/bin/federation/auth/token-signer.js.map +0 -1
  182. package/bin/service/ai/charge.d.ts +0 -76
  183. package/bin/service/ai/charge.d.ts.map +0 -1
  184. package/bin/service/ai/charge.js +0 -10
  185. package/bin/service/ai/charge.js.map +0 -1
  186. package/bin/service/ai/job-types.d.ts +0 -69
  187. package/bin/service/ai/job-types.d.ts.map +0 -1
  188. package/bin/service/ai/job-types.js +0 -9
  189. package/bin/service/ai/job-types.js.map +0 -1
  190. package/bin/service/ai/provider.d.ts +0 -143
  191. package/bin/service/ai/provider.d.ts.map +0 -1
  192. package/bin/service/ai/provider.js +0 -221
  193. package/bin/service/ai/provider.js.map +0 -1
  194. package/bin/service/ai/types.d.ts +0 -174
  195. package/bin/service/ai/types.d.ts.map +0 -1
  196. package/bin/service/ai/types.js +0 -7
  197. package/bin/service/ai/types.js.map +0 -1
  198. package/bin/types/AIImageJobClaim.d.ts +0 -14
  199. package/bin/types/AIImageJobClaim.d.ts.map +0 -1
  200. package/bin/types/AIImageJobClaim.js +0 -7
  201. package/bin/types/AIImageJobClaim.js.map +0 -1
  202. package/bin/types/AIReasoning.d.ts +0 -25
  203. package/bin/types/AIReasoning.d.ts.map +0 -1
  204. package/bin/types/AIReasoning.js +0 -7
  205. package/bin/types/AIReasoning.js.map +0 -1
  206. package/bin/types/AIRouting.d.ts +0 -37
  207. package/bin/types/AIRouting.d.ts.map +0 -1
  208. package/bin/types/AIRouting.js +0 -7
  209. package/bin/types/AIRouting.js.map +0 -1
  210. package/bin/types/CityLanguageModelRuntime.d.ts +0 -46
  211. package/bin/types/CityLanguageModelRuntime.d.ts.map +0 -1
  212. package/bin/types/CityLanguageModelRuntime.js +0 -8
  213. package/bin/types/CityLanguageModelRuntime.js.map +0 -1
  214. package/bin/types/CityLanguageModelTransport.d.ts +0 -49
  215. package/bin/types/CityLanguageModelTransport.d.ts.map +0 -1
  216. package/bin/types/CityLanguageModelTransport.js +0 -9
  217. package/bin/types/CityLanguageModelTransport.js.map +0 -1
  218. package/bin/types/CityModelRuntime.d.ts +0 -17
  219. package/bin/types/CityModelRuntime.d.ts.map +0 -1
  220. package/bin/types/CityModelRuntime.js +0 -8
  221. package/bin/types/CityModelRuntime.js.map +0 -1
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Federation AIChannel 基类模块。
3
+ *
4
+ * AIChannel 是 Federation 服务端的上游 AI 执行渠道。语言能力只通过标准
5
+ * LanguageModelV3 `stream(input)` 暴露,text action 由同一标准流自动派生。
6
+ */
7
+ import { execute_language_model_text } from "./language-model-text.js";
8
+ import { read_resolved_reasoning } from "./reasoning.js";
9
+ /** Federation 服务端 AI 执行渠道。 */
10
+ export class AIChannel {
11
+ /** Channel 唯一 ID。 */
12
+ id;
13
+ /** Channel 所需环境变量及管理员说明。 */
14
+ env;
15
+ /** 上游 API 根地址,由子类显式使用。 */
16
+ base_url;
17
+ /** 默认 API Key 对应的 Federation env key。 */
18
+ env_key;
19
+ /** reasoning 映射使用的 AI SDK providerOptions 命名空间。 */
20
+ ai_sdk_provider_id;
21
+ /** Channel 级 AI SDK providerOptions 默认值。 */
22
+ ai_sdk_provider_options;
23
+ constructor(options) {
24
+ this.id = options.id;
25
+ this.env = options.env ?? (options.env_key
26
+ ? { [options.env_key]: `${options.id} API Key` }
27
+ : undefined);
28
+ this.base_url = options.base_url;
29
+ this.env_key = options.env_key;
30
+ this.ai_sdk_provider_id = options.ai_sdk_provider_id?.trim() || undefined;
31
+ this.ai_sdk_provider_options = clone_provider_options(options.ai_sdk_provider_options);
32
+ }
33
+ /**
34
+ * 把 AIService 已校验 reasoning 映射成真实 AI SDK Provider 的选项。
35
+ *
36
+ * 默认实现适用于使用 `reasoningEffort` 的 Provider;其它 Provider 应覆盖。
37
+ */
38
+ build_reasoning_provider_options(input) {
39
+ const reasoning = input.reasoning;
40
+ if (!reasoning)
41
+ return undefined;
42
+ const provider_id = this.ai_sdk_provider_id;
43
+ if (!provider_id) {
44
+ throw new Error(`AIChannel ${this.id} requires ai_sdk_provider_id to map reasoning options`);
45
+ }
46
+ return {
47
+ [provider_id]: {
48
+ reasoningEffort: reasoning.effort,
49
+ },
50
+ };
51
+ }
52
+ /** 构建传给 Channel stream 的完整显式输入。 */
53
+ build_stream_input(ctx, call, model, model_provider_options) {
54
+ const { providerOptions: _client_provider_options, ...safe_call } = call;
55
+ const reasoning = read_resolved_reasoning(ctx);
56
+ const base_input = {
57
+ call: safe_call,
58
+ model,
59
+ env: (key) => ctx.env(key),
60
+ ...(reasoning ? { reasoning } : {}),
61
+ };
62
+ const provider_options = merge_provider_options(this.ai_sdk_provider_options, model_provider_options, this.build_reasoning_provider_options(base_input));
63
+ return {
64
+ ...base_input,
65
+ call: {
66
+ ...safe_call,
67
+ ...(provider_options ? { providerOptions: provider_options } : {}),
68
+ },
69
+ };
70
+ }
71
+ /** 模型成功完成后生成账单草稿。 */
72
+ bill(_input) {
73
+ return undefined;
74
+ }
75
+ /** 把当前 Channel 下的模型声明转换为 Federation 内部模型定义。 */
76
+ model(spec) {
77
+ const model_provider_options = clone_provider_options(spec.ai_sdk_provider_options);
78
+ const actions = {};
79
+ const channel_model = Object.freeze({
80
+ id: spec.id,
81
+ upstream_model: spec.upstream_model,
82
+ });
83
+ const modalities = [
84
+ "image_create",
85
+ "image_fetch",
86
+ "image_result",
87
+ "video",
88
+ "tts",
89
+ "asr",
90
+ ];
91
+ for (const modality of modalities) {
92
+ const fn = this[modality];
93
+ if (typeof fn === "function") {
94
+ const action = fn.bind(this);
95
+ actions[modality] = (ctx) => action(this.build_action_input(ctx, channel_model));
96
+ }
97
+ }
98
+ const stream = this.stream
99
+ ? (ctx, call) => this.stream(this.build_stream_input(ctx, call, channel_model, model_provider_options))
100
+ : undefined;
101
+ if (stream) {
102
+ actions.text = (ctx) => execute_language_model_text(ctx, stream, this.id);
103
+ }
104
+ const { ai_sdk_provider_options: _ai_sdk_provider_options, ...model_spec } = spec;
105
+ return {
106
+ ...model_spec,
107
+ channel_id: this.id,
108
+ ...(spec.price ? { price: [...spec.price] } : {}),
109
+ env: this.env,
110
+ runtime: {
111
+ ...(stream ? { stream } : {}),
112
+ actions,
113
+ },
114
+ bill: spec.bill ?? this.bill.bind(this),
115
+ };
116
+ }
117
+ /** 从通用 Action Context 构造 Channel 允许访问的领域输入。 */
118
+ build_action_input(ctx, model) {
119
+ const image_job = ctx.locals.ai_image_job;
120
+ return {
121
+ input: ctx.input,
122
+ model,
123
+ env: (key) => ctx.env(key),
124
+ ...(ctx.user?.user_id ? { user_id: ctx.user.user_id } : {}),
125
+ ...(ctx.city?.city_id ? { city_id: ctx.city.city_id } : {}),
126
+ ...(image_job && typeof image_job === "object"
127
+ ? { image_job: image_job }
128
+ : {}),
129
+ };
130
+ }
131
+ }
132
+ /** 复制服务端 providerOptions,避免调用方原地修改已注册配置。 */
133
+ function clone_provider_options(input) {
134
+ return input ? structuredClone(input) : undefined;
135
+ }
136
+ /** 按 AI SDK Provider ID 浅合并 providerOptions。 */
137
+ function merge_provider_options(...sources) {
138
+ const merged = {};
139
+ for (const source of sources) {
140
+ if (!source)
141
+ continue;
142
+ for (const [provider_id, options] of Object.entries(source)) {
143
+ merged[provider_id] = {
144
+ ...merged[provider_id],
145
+ ...options,
146
+ };
147
+ }
148
+ }
149
+ return Object.keys(merged).length > 0 ? merged : undefined;
150
+ }
151
+ //# sourceMappingURL=AIChannel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AIChannel.js","sourceRoot":"","sources":["../../../src/service/ai/AIChannel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * OpenAI Chat Completions 与 AI SDK LanguageModelV3 的协议适配模块。
3
+ *
4
+ * 本模块只负责 HTTP 协议转换,不负责模型选择、fallback、reasoning 策略、
5
+ * providerOptions 或计费。所有请求在进入 AIChannel 前都已变为标准 V3 call。
6
+ */
7
+ import type { LanguageModelV3CallOptions, LanguageModelV3GenerateResult, LanguageModelV3StreamResult } from "../../types/AI.js";
8
+ import type { OpenAIChatCompletionRequest } from "../../types/AITransport.js";
9
+ /** 将 OpenAI Chat Completions 请求转换为标准 LanguageModelV3 调用参数。 */
10
+ export declare function openai_chat_request_to_language_model_call(request: OpenAIChatCompletionRequest, signal?: AbortSignal): LanguageModelV3CallOptions;
11
+ /** 把标准 V3 流输出转换成 OpenAI JSON 或 SSE Response。 */
12
+ export declare function create_openai_chat_completion_response(input: {
13
+ /** Federation 对外模型 ID。 */
14
+ model_id: string;
15
+ /** 是否返回 SSE。 */
16
+ stream: boolean;
17
+ /** AIChannel 返回的标准 V3 流。 */
18
+ result: LanguageModelV3StreamResult;
19
+ }): Promise<{
20
+ response: Response;
21
+ completion: Promise<LanguageModelV3GenerateResult | undefined>;
22
+ }>;
23
+ //# sourceMappingURL=OpenAIChatCompletionsAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenAIChatCompletionsAdapter.d.ts","sourceRoot":"","sources":["../../../src/service/ai/OpenAIChatCompletionsAdapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,0BAA0B,EAC1B,6BAA6B,EAE7B,2BAA2B,EAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,2BAA2B,EAO5B,MAAM,4BAA4B,CAAC;AAUpC,8DAA8D;AAC9D,wBAAgB,0CAA0C,CACxD,OAAO,EAAE,2BAA2B,EACpC,MAAM,CAAC,EAAE,WAAW,GACnB,0BAA0B,CAsC5B;AAED,gDAAgD;AAChD,wBAAsB,sCAAsC,CAAC,KAAK,EAAE;IAClE,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,4BAA4B;IAC5B,MAAM,EAAE,2BAA2B,CAAC;CACrC,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC,6BAA6B,GAAG,SAAS,CAAC,CAAA;CAAE,CAAC,CAWlG"}
@@ -0,0 +1,503 @@
1
+ /**
2
+ * OpenAI Chat Completions 与 AI SDK LanguageModelV3 的协议适配模块。
3
+ *
4
+ * 本模块只负责 HTTP 协议转换,不负责模型选择、fallback、reasoning 策略、
5
+ * providerOptions 或计费。所有请求在进入 AIChannel 前都已变为标准 V3 call。
6
+ */
7
+ import { collect_city_language_model_stream } from "../../utils/CityLanguageModelResult.js";
8
+ /** OpenAI SSE 响应头。 */
9
+ const OPENAI_SSE_HEADERS = {
10
+ "content-type": "text/event-stream; charset=utf-8",
11
+ "cache-control": "no-cache, no-transform",
12
+ connection: "keep-alive",
13
+ };
14
+ /** 将 OpenAI Chat Completions 请求转换为标准 LanguageModelV3 调用参数。 */
15
+ export function openai_chat_request_to_language_model_call(request, signal) {
16
+ if (!Array.isArray(request.messages)) {
17
+ throw create_request_error("messages must be an array");
18
+ }
19
+ const max_output_tokens = read_optional_number(request.max_completion_tokens ?? request.max_tokens);
20
+ const stop_sequences = typeof request.stop === "string"
21
+ ? [request.stop]
22
+ : Array.isArray(request.stop)
23
+ ? request.stop.filter((item) => typeof item === "string")
24
+ : undefined;
25
+ const tools = convert_tools(request.tools);
26
+ const tool_choice = convert_tool_choice(request.tool_choice);
27
+ const response_format = convert_response_format(request.response_format);
28
+ return {
29
+ prompt: convert_messages(request.messages),
30
+ ...(max_output_tokens !== undefined ? { maxOutputTokens: max_output_tokens } : {}),
31
+ ...(read_optional_number(request.temperature) !== undefined
32
+ ? { temperature: read_optional_number(request.temperature) }
33
+ : {}),
34
+ ...(read_optional_number(request.top_p) !== undefined
35
+ ? { topP: read_optional_number(request.top_p) }
36
+ : {}),
37
+ ...(stop_sequences?.length ? { stopSequences: stop_sequences } : {}),
38
+ ...(read_optional_number(request.presence_penalty) !== undefined
39
+ ? { presencePenalty: read_optional_number(request.presence_penalty) }
40
+ : {}),
41
+ ...(read_optional_number(request.frequency_penalty) !== undefined
42
+ ? { frequencyPenalty: read_optional_number(request.frequency_penalty) }
43
+ : {}),
44
+ ...(Number.isInteger(request.seed) ? { seed: request.seed } : {}),
45
+ ...(tools?.length ? { tools } : {}),
46
+ ...(tool_choice ? { toolChoice: tool_choice } : {}),
47
+ ...(response_format ? { responseFormat: response_format } : {}),
48
+ ...(signal ? { abortSignal: signal } : {}),
49
+ };
50
+ }
51
+ /** 把标准 V3 流输出转换成 OpenAI JSON 或 SSE Response。 */
52
+ export async function create_openai_chat_completion_response(input) {
53
+ if (input.stream)
54
+ return create_stream_response(input.model_id, input.result);
55
+ const completion = collect_city_language_model_stream(input.result.stream, input.result.request?.body);
56
+ const result = await completion;
57
+ return {
58
+ response: Response.json(create_json_response(input.model_id, result)),
59
+ completion: Promise.resolve(result),
60
+ };
61
+ }
62
+ /** 创建非流式 OpenAI Chat Completion JSON。 */
63
+ function create_json_response(model_id, result) {
64
+ const metadata = result.response;
65
+ const tool_calls = result.content
66
+ .filter((part) => part.type === "tool-call")
67
+ .map((part) => ({
68
+ id: part.toolCallId,
69
+ type: "function",
70
+ function: {
71
+ name: part.toolName,
72
+ arguments: serialize_tool_input(part.input),
73
+ },
74
+ }));
75
+ const content = result.content
76
+ .filter((part) => part.type === "text")
77
+ .map((part) => part.text)
78
+ .join("");
79
+ const reasoning_content = result.content
80
+ .filter((part) => part.type === "reasoning")
81
+ .map((part) => part.text)
82
+ .join("");
83
+ return {
84
+ id: metadata?.id ?? `chatcmpl_${crypto.randomUUID()}`,
85
+ object: "chat.completion",
86
+ created: to_unix_timestamp(metadata?.timestamp),
87
+ model: model_id,
88
+ choices: [{
89
+ index: 0,
90
+ message: {
91
+ role: "assistant",
92
+ content: content || null,
93
+ ...(reasoning_content ? { reasoning_content } : {}),
94
+ ...(tool_calls.length ? { tool_calls } : {}),
95
+ },
96
+ finish_reason: to_openai_finish_reason(result.finishReason),
97
+ }],
98
+ usage: to_openai_usage(result.usage),
99
+ };
100
+ }
101
+ /** 创建 OpenAI Chat Completions SSE,并在消费过程中聚合计费所需结果。 */
102
+ function create_stream_response(model_id, result) {
103
+ const reader = result.stream.getReader();
104
+ const encoder = new TextEncoder();
105
+ const response_id = `chatcmpl_${crypto.randomUUID()}`;
106
+ let resolved_response_id = response_id;
107
+ let created = Math.floor(Date.now() / 1000);
108
+ let resolve_completion = () => undefined;
109
+ const completion = new Promise((resolve) => {
110
+ resolve_completion = resolve;
111
+ });
112
+ const collected_parts = [];
113
+ const tool_indexes = new Map();
114
+ const streamed_tool_ids = new Set();
115
+ let next_tool_index = 0;
116
+ let completed = false;
117
+ let sent_role = false;
118
+ const body = new ReadableStream({
119
+ async pull(controller) {
120
+ try {
121
+ while (true) {
122
+ const chunk = await reader.read();
123
+ if (chunk.done) {
124
+ await complete_stream();
125
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
126
+ controller.close();
127
+ return;
128
+ }
129
+ const part = chunk.value;
130
+ collected_parts.push(part);
131
+ if (part.type === "response-metadata") {
132
+ if (part.id)
133
+ resolved_response_id = part.id;
134
+ created = to_unix_timestamp(part.timestamp);
135
+ }
136
+ if (part.type === "finish") {
137
+ if (!sent_role) {
138
+ sent_role = true;
139
+ controller.enqueue(encoder.encode(serialize_sse_chunk({
140
+ delta: { role: "assistant" },
141
+ })));
142
+ }
143
+ controller.enqueue(encoder.encode(serialize_sse_chunk({
144
+ delta: {},
145
+ finish_reason: to_openai_finish_reason(part.finishReason),
146
+ usage: to_openai_usage(part.usage),
147
+ })));
148
+ return;
149
+ }
150
+ const payloads = stream_part_to_openai_chunks(part, {
151
+ get_tool_index: (tool_call_id) => {
152
+ const existing = tool_indexes.get(tool_call_id);
153
+ if (existing !== undefined)
154
+ return existing;
155
+ const index = next_tool_index;
156
+ next_tool_index += 1;
157
+ tool_indexes.set(tool_call_id, index);
158
+ return index;
159
+ },
160
+ streamed_tool_ids,
161
+ });
162
+ if (!sent_role && payloads.length > 0) {
163
+ sent_role = true;
164
+ payloads.unshift({ role: "assistant" });
165
+ }
166
+ if (payloads.length === 0)
167
+ continue;
168
+ for (const delta of payloads) {
169
+ controller.enqueue(encoder.encode(serialize_sse_chunk({ delta })));
170
+ }
171
+ return;
172
+ }
173
+ }
174
+ catch (error) {
175
+ complete(undefined);
176
+ controller.error(error);
177
+ }
178
+ },
179
+ async cancel(reason) {
180
+ await reader.cancel(reason);
181
+ complete(undefined);
182
+ },
183
+ });
184
+ return {
185
+ response: new Response(body, { status: 200, headers: OPENAI_SSE_HEADERS }),
186
+ completion,
187
+ };
188
+ /** 序列化一个 OpenAI chunk。 */
189
+ function serialize_sse_chunk(input) {
190
+ return `data: ${JSON.stringify({
191
+ id: resolved_response_id,
192
+ object: "chat.completion.chunk",
193
+ created,
194
+ model: model_id,
195
+ choices: [{
196
+ index: 0,
197
+ delta: input.delta,
198
+ finish_reason: input.finish_reason ?? null,
199
+ }],
200
+ ...(input.usage ? { usage: input.usage } : {}),
201
+ })}\n\n`;
202
+ }
203
+ /** 在上游结束时输出 finish chunk 并构造标准聚合结果。 */
204
+ async function complete_stream() {
205
+ const replay = new ReadableStream({
206
+ start(replay_controller) {
207
+ for (const part of collected_parts)
208
+ replay_controller.enqueue(part);
209
+ replay_controller.close();
210
+ },
211
+ });
212
+ try {
213
+ complete(await collect_city_language_model_stream(replay, result.request?.body));
214
+ }
215
+ catch {
216
+ complete(undefined);
217
+ }
218
+ }
219
+ /** 只结算一次 completion。 */
220
+ function complete(value) {
221
+ if (completed)
222
+ return;
223
+ completed = true;
224
+ resolve_completion(value);
225
+ }
226
+ }
227
+ /** 将一个 V3 流事件转换成零个或多个 OpenAI delta。 */
228
+ function stream_part_to_openai_chunks(part, state) {
229
+ if (part.type === "text-delta")
230
+ return [{ content: part.delta }];
231
+ if (part.type === "reasoning-delta")
232
+ return [{ reasoning_content: part.delta }];
233
+ if (part.type === "tool-input-start") {
234
+ state.streamed_tool_ids.add(part.id);
235
+ return [{
236
+ tool_calls: [{
237
+ index: state.get_tool_index(part.id),
238
+ id: part.id,
239
+ type: "function",
240
+ function: { name: part.toolName, arguments: "" },
241
+ }],
242
+ }];
243
+ }
244
+ if (part.type === "tool-input-delta") {
245
+ state.streamed_tool_ids.add(part.id);
246
+ return [{
247
+ tool_calls: [{
248
+ index: state.get_tool_index(part.id),
249
+ id: part.id,
250
+ function: { arguments: part.delta },
251
+ }],
252
+ }];
253
+ }
254
+ if (part.type === "tool-call") {
255
+ if (state.streamed_tool_ids.has(part.toolCallId))
256
+ return [];
257
+ return [{
258
+ tool_calls: [{
259
+ index: state.get_tool_index(part.toolCallId),
260
+ id: part.toolCallId,
261
+ type: "function",
262
+ function: {
263
+ name: part.toolName,
264
+ arguments: serialize_tool_input(part.input),
265
+ },
266
+ }],
267
+ }];
268
+ }
269
+ if (part.type === "error")
270
+ throw part.error;
271
+ return [];
272
+ }
273
+ /** 将 OpenAI 消息列表转换成标准 V3 prompt。 */
274
+ function convert_messages(messages) {
275
+ const prompt = [];
276
+ const tool_names = new Map();
277
+ for (const message of messages) {
278
+ if (!message || typeof message !== "object")
279
+ throw create_request_error("message must be an object");
280
+ if (message.role === "system" || message.role === "developer") {
281
+ prompt.push({ role: "system", content: read_text_content(message.content) });
282
+ continue;
283
+ }
284
+ if (message.role === "user") {
285
+ prompt.push({ role: "user", content: convert_user_content(message.content) });
286
+ continue;
287
+ }
288
+ if (message.role === "assistant") {
289
+ const content = [];
290
+ if (Array.isArray(message.content)) {
291
+ content.push(...convert_user_content(message.content));
292
+ }
293
+ else {
294
+ const text = read_optional_text_content(message.content);
295
+ if (text)
296
+ content.push({ type: "text", text });
297
+ }
298
+ for (const tool_call of message.tool_calls ?? []) {
299
+ tool_names.set(tool_call.id, tool_call.function.name);
300
+ content.push({
301
+ type: "tool-call",
302
+ toolCallId: tool_call.id,
303
+ toolName: tool_call.function.name,
304
+ input: parse_json_or_text(tool_call.function.arguments),
305
+ });
306
+ }
307
+ prompt.push({ role: "assistant", content });
308
+ continue;
309
+ }
310
+ if (message.role === "tool") {
311
+ const tool_call_id = read_required_string(message.tool_call_id, "tool_call_id");
312
+ prompt.push({
313
+ role: "tool",
314
+ content: [{
315
+ type: "tool-result",
316
+ toolCallId: tool_call_id,
317
+ toolName: message.name ?? tool_names.get(tool_call_id) ?? "tool",
318
+ output: create_tool_output(message.content),
319
+ }],
320
+ });
321
+ continue;
322
+ }
323
+ throw create_request_error(`unsupported message role: ${String(message.role)}`);
324
+ }
325
+ return prompt;
326
+ }
327
+ /** 转换 user 的文本、图片和文件内容。 */
328
+ function convert_user_content(content) {
329
+ if (typeof content === "string")
330
+ return [{ type: "text", text: content }];
331
+ if (content == null)
332
+ return [];
333
+ if (!Array.isArray(content))
334
+ throw create_request_error("message content must be a string or array");
335
+ return content.map((part) => convert_user_part(part));
336
+ }
337
+ /** 转换单个 OpenAI user content part。 */
338
+ function convert_user_part(part) {
339
+ if (part.type === "text" || part.type === "input_text") {
340
+ return { type: "text", text: read_required_string(part.text, "content.text") };
341
+ }
342
+ if (part.type === "image_url" || part.type === "input_image") {
343
+ const url = part.url ?? part.image_url?.url;
344
+ return {
345
+ type: "file",
346
+ data: to_url_or_data(read_required_string(url, "content.image_url.url")),
347
+ mediaType: infer_media_type(url, "image/*"),
348
+ };
349
+ }
350
+ if (part.type === "file") {
351
+ return {
352
+ type: "file",
353
+ data: to_url_or_data(read_required_string(part.url, "content.url")),
354
+ mediaType: part.media_type ?? part.mediaType ?? infer_media_type(part.url, "application/octet-stream"),
355
+ ...(part.filename ? { filename: part.filename } : {}),
356
+ };
357
+ }
358
+ throw create_request_error(`unsupported content type: ${String(part.type)}`);
359
+ }
360
+ /** 转换 OpenAI function tools。 */
361
+ function convert_tools(tools) {
362
+ if (!tools)
363
+ return undefined;
364
+ return tools.map((tool) => {
365
+ if (tool.type !== "function" || !tool.function?.name) {
366
+ throw create_request_error("only named function tools are supported");
367
+ }
368
+ return {
369
+ type: "function",
370
+ name: tool.function.name,
371
+ ...(tool.function.description ? { description: tool.function.description } : {}),
372
+ inputSchema: tool.function.parameters ?? {},
373
+ ...(tool.function.strict !== undefined ? { strict: tool.function.strict } : {}),
374
+ };
375
+ });
376
+ }
377
+ /** 转换 OpenAI tool_choice。 */
378
+ function convert_tool_choice(choice) {
379
+ if (!choice)
380
+ return undefined;
381
+ if (typeof choice === "string")
382
+ return { type: choice };
383
+ const tool_name = choice.function?.name;
384
+ if (!tool_name)
385
+ throw create_request_error("tool_choice.function.name is required");
386
+ return { type: "tool", toolName: tool_name };
387
+ }
388
+ /** 转换 OpenAI response_format。 */
389
+ function convert_response_format(format) {
390
+ if (!format || format.type === "text")
391
+ return format ? { type: "text" } : undefined;
392
+ if (format.type === "json_object")
393
+ return { type: "json" };
394
+ return {
395
+ type: "json",
396
+ name: format.json_schema.name,
397
+ ...(format.json_schema.description ? { description: format.json_schema.description } : {}),
398
+ schema: format.json_schema.schema,
399
+ };
400
+ }
401
+ /** 将 V3 finish reason 映射为 OpenAI finish_reason。 */
402
+ function to_openai_finish_reason(reason) {
403
+ const unified = reason.unified;
404
+ if (unified === "stop")
405
+ return "stop";
406
+ if (unified === "length")
407
+ return "length";
408
+ if (unified === "tool-calls")
409
+ return "tool_calls";
410
+ if (unified === "content-filter")
411
+ return "content_filter";
412
+ return "stop";
413
+ }
414
+ /** 将 V3 usage 映射为 OpenAI usage。 */
415
+ function to_openai_usage(usage) {
416
+ const prompt_tokens = usage.inputTokens.total ?? 0;
417
+ const completion_tokens = usage.outputTokens.total ?? 0;
418
+ const cached_tokens = usage.inputTokens.cacheRead ?? 0;
419
+ const reasoning_tokens = usage.outputTokens.reasoning ?? 0;
420
+ return {
421
+ prompt_tokens,
422
+ completion_tokens,
423
+ total_tokens: prompt_tokens + completion_tokens,
424
+ ...(cached_tokens > 0 ? { prompt_tokens_details: { cached_tokens } } : {}),
425
+ ...(reasoning_tokens > 0 ? { completion_tokens_details: { reasoning_tokens } } : {}),
426
+ };
427
+ }
428
+ /** 将工具输入稳定序列化为 JSON 字符串。 */
429
+ function serialize_tool_input(input) {
430
+ if (typeof input === "string")
431
+ return input;
432
+ try {
433
+ return JSON.stringify(input ?? {});
434
+ }
435
+ catch {
436
+ return "{}";
437
+ }
438
+ }
439
+ /** 将 JSON 字符串解析为工具输入,非法 JSON 保留为文本。 */
440
+ function parse_json_or_text(input) {
441
+ try {
442
+ return JSON.parse(input);
443
+ }
444
+ catch {
445
+ return input;
446
+ }
447
+ }
448
+ /** 构造标准 V3 tool result output。 */
449
+ function create_tool_output(content) {
450
+ const value = read_text_content(content);
451
+ try {
452
+ return { type: "json", value: JSON.parse(value) };
453
+ }
454
+ catch {
455
+ return { type: "text", value };
456
+ }
457
+ }
458
+ /** 从消息内容读取纯文本。 */
459
+ function read_text_content(content) {
460
+ return read_optional_text_content(content) ?? "";
461
+ }
462
+ /** 从消息内容读取可选纯文本。 */
463
+ function read_optional_text_content(content) {
464
+ if (typeof content === "string")
465
+ return content;
466
+ if (!Array.isArray(content))
467
+ return undefined;
468
+ const text = content
469
+ .filter((part) => part.type === "text" || part.type === "input_text")
470
+ .map((part) => part.text)
471
+ .join("\n");
472
+ return text || undefined;
473
+ }
474
+ /** HTTP URL 转成 URL 对象,Data URL 保持字符串。 */
475
+ function to_url_or_data(value) {
476
+ return /^https?:\/\//iu.test(value) ? new URL(value) : value;
477
+ }
478
+ /** 从 Data URL 推断媒体类型,普通 URL 使用 fallback。 */
479
+ function infer_media_type(value, fallback) {
480
+ const match = value?.match(/^data:([^;,]+)[;,]/iu);
481
+ return match?.[1] ?? fallback;
482
+ }
483
+ /** 把 Date 转换成 OpenAI Unix 秒级时间戳。 */
484
+ function to_unix_timestamp(value) {
485
+ return value ? Math.floor(value.getTime() / 1000) : Math.floor(Date.now() / 1000);
486
+ }
487
+ /** 读取有限数字。 */
488
+ function read_optional_number(value) {
489
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
490
+ }
491
+ /** 读取必填字符串。 */
492
+ function read_required_string(value, field) {
493
+ if (typeof value !== "string" || !value.trim())
494
+ throw create_request_error(`${field} is required`);
495
+ return value;
496
+ }
497
+ /** 创建会映射为 HTTP 422 的请求错误。 */
498
+ function create_request_error(message) {
499
+ const error = new Error(message);
500
+ error.statusCode = 422;
501
+ return error;
502
+ }
503
+ //# sourceMappingURL=OpenAIChatCompletionsAdapter.js.map