@lark-base-open/base-database-cli 1.1.1 → 1.3.7

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 (222) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +6 -0
  3. package/README.md +45 -27
  4. package/dist/ai-functions.d.ts +42 -0
  5. package/dist/ai-functions.d.ts.map +1 -0
  6. package/dist/ai-functions.js +327 -0
  7. package/dist/ai-functions.js.map +1 -0
  8. package/dist/archive.d.ts +21 -0
  9. package/dist/archive.d.ts.map +1 -1
  10. package/dist/archive.js +65 -10
  11. package/dist/archive.js.map +1 -1
  12. package/dist/auth.d.ts +5 -0
  13. package/dist/auth.d.ts.map +1 -1
  14. package/dist/auth.js +26 -3
  15. package/dist/auth.js.map +1 -1
  16. package/dist/check.d.ts +74 -0
  17. package/dist/check.d.ts.map +1 -0
  18. package/dist/check.js +283 -0
  19. package/dist/check.js.map +1 -0
  20. package/dist/dependency-policy.d.ts +10 -0
  21. package/dist/dependency-policy.d.ts.map +1 -0
  22. package/dist/dependency-policy.js +162 -0
  23. package/dist/dependency-policy.js.map +1 -0
  24. package/dist/deploy.d.ts +1 -0
  25. package/dist/deploy.d.ts.map +1 -1
  26. package/dist/deploy.js +122 -175
  27. package/dist/deploy.js.map +1 -1
  28. package/dist/environment-variables.d.ts +52 -0
  29. package/dist/environment-variables.d.ts.map +1 -0
  30. package/dist/environment-variables.js +563 -0
  31. package/dist/environment-variables.js.map +1 -0
  32. package/dist/environment.d.ts +1 -1
  33. package/dist/environment.js +1 -1
  34. package/dist/environment.js.map +1 -1
  35. package/dist/function-input-types.d.ts +10 -0
  36. package/dist/function-input-types.d.ts.map +1 -0
  37. package/dist/function-input-types.js +160 -0
  38. package/dist/function-input-types.js.map +1 -0
  39. package/dist/function-schema.d.ts.map +1 -1
  40. package/dist/function-schema.js +89 -36
  41. package/dist/function-schema.js.map +1 -1
  42. package/dist/init-field-retry.d.ts +17 -0
  43. package/dist/init-field-retry.d.ts.map +1 -0
  44. package/dist/init-field-retry.js +127 -0
  45. package/dist/init-field-retry.js.map +1 -0
  46. package/dist/init-journal.d.ts +98 -0
  47. package/dist/init-journal.d.ts.map +1 -0
  48. package/dist/init-journal.js +492 -0
  49. package/dist/init-journal.js.map +1 -0
  50. package/dist/init-schema.d.ts +8 -0
  51. package/dist/init-schema.d.ts.map +1 -0
  52. package/dist/init-schema.js +423 -0
  53. package/dist/init-schema.js.map +1 -0
  54. package/dist/init.d.ts +14 -0
  55. package/dist/init.d.ts.map +1 -1
  56. package/dist/init.js +883 -261
  57. package/dist/init.js.map +1 -1
  58. package/dist/lark-cli-error.d.ts +46 -0
  59. package/dist/lark-cli-error.d.ts.map +1 -0
  60. package/dist/lark-cli-error.js +171 -0
  61. package/dist/lark-cli-error.js.map +1 -0
  62. package/dist/lark-cli.d.ts +1 -1
  63. package/dist/lark-cli.d.ts.map +1 -1
  64. package/dist/lark-cli.js +66 -18
  65. package/dist/lark-cli.js.map +1 -1
  66. package/dist/mutation-schema.d.ts +146 -0
  67. package/dist/mutation-schema.d.ts.map +1 -0
  68. package/dist/mutation-schema.js +153 -0
  69. package/dist/mutation-schema.js.map +1 -0
  70. package/dist/mutation.d.ts +10 -0
  71. package/dist/mutation.d.ts.map +1 -0
  72. package/dist/mutation.js +131 -0
  73. package/dist/mutation.js.map +1 -0
  74. package/dist/npm.d.ts +1 -0
  75. package/dist/npm.d.ts.map +1 -1
  76. package/dist/npm.js +2 -2
  77. package/dist/npm.js.map +1 -1
  78. package/dist/preview-receipt.d.ts.map +1 -1
  79. package/dist/preview-receipt.js +15 -5
  80. package/dist/preview-receipt.js.map +1 -1
  81. package/dist/process.d.ts +3 -1
  82. package/dist/process.d.ts.map +1 -1
  83. package/dist/process.js +16 -4
  84. package/dist/process.js.map +1 -1
  85. package/dist/program.d.ts.map +1 -1
  86. package/dist/program.js +240 -41
  87. package/dist/program.js.map +1 -1
  88. package/dist/project-build.d.ts +25 -0
  89. package/dist/project-build.d.ts.map +1 -0
  90. package/dist/project-build.js +229 -0
  91. package/dist/project-build.js.map +1 -0
  92. package/dist/project-context.d.ts +344 -0
  93. package/dist/project-context.d.ts.map +1 -0
  94. package/dist/project-context.js +485 -0
  95. package/dist/project-context.js.map +1 -0
  96. package/dist/project-layout.d.ts +5 -0
  97. package/dist/project-layout.d.ts.map +1 -0
  98. package/dist/project-layout.js +24 -0
  99. package/dist/project-layout.js.map +1 -0
  100. package/dist/public-sql-error.d.ts +25 -0
  101. package/dist/public-sql-error.d.ts.map +1 -0
  102. package/dist/public-sql-error.js +81 -0
  103. package/dist/public-sql-error.js.map +1 -0
  104. package/dist/pull.d.ts.map +1 -1
  105. package/dist/pull.js +54 -15
  106. package/dist/pull.js.map +1 -1
  107. package/dist/site-readme.d.ts +3 -0
  108. package/dist/site-readme.d.ts.map +1 -0
  109. package/dist/site-readme.js +83 -0
  110. package/dist/site-readme.js.map +1 -0
  111. package/dist/workspace-fingerprint.d.ts +21 -1
  112. package/dist/workspace-fingerprint.d.ts.map +1 -1
  113. package/dist/workspace-fingerprint.js +81 -2
  114. package/dist/workspace-fingerprint.js.map +1 -1
  115. package/package.json +2 -1
  116. package/skills/lark-base-database/SKILL.md +66 -50
  117. package/skills/lark-base-database/references/ai-function.md +471 -0
  118. package/skills/lark-base-database/references/base-capability-routing.md +3 -3
  119. package/skills/lark-base-database/references/database-function-limitations.md +46 -0
  120. package/skills/lark-base-database/references/database-function.md +349 -0
  121. package/skills/lark-base-database/references/design/components/actions-fields.md +16 -0
  122. package/skills/lark-base-database/references/design/components/code/controls.css +122 -0
  123. package/skills/lark-base-database/references/design/components/code/controls.tsx +73 -0
  124. package/skills/lark-base-database/references/design/components/code/date-picker.tsx +42 -0
  125. package/skills/lark-base-database/references/design/components/code/floating.tsx +41 -0
  126. package/skills/lark-base-database/references/design/components/code/overlays.tsx +19 -0
  127. package/skills/lark-base-database/references/design/components/code/select.tsx +34 -0
  128. package/skills/lark-base-database/references/design/components/code/styles.d.ts +1 -0
  129. package/skills/lark-base-database/references/design/components/code/theme.css +49 -0
  130. package/skills/lark-base-database/references/design/components/code/usage.tsx +38 -0
  131. package/skills/lark-base-database/references/design/components/date-time-number.md +9 -0
  132. package/skills/lark-base-database/references/design/components/feedback-navigation.md +9 -0
  133. package/skills/lark-base-database/references/design/components/index.md +18 -0
  134. package/skills/lark-base-database/references/design/components/overlays.md +9 -0
  135. package/skills/lark-base-database/references/design/components/patterns/flat-business.md +7 -0
  136. package/skills/lark-base-database/references/design/components/selection.md +24 -0
  137. package/skills/lark-base-database/references/design/components.md +91 -0
  138. package/skills/lark-base-database/references/design/composition.md +90 -0
  139. package/skills/lark-base-database/references/design/control-recipes.md +107 -0
  140. package/skills/lark-base-database/references/design/examples.md +172 -0
  141. package/skills/lark-base-database/references/design/foundations.md +126 -0
  142. package/skills/lark-base-database/references/design/review.md +82 -0
  143. package/skills/lark-base-database/references/design/scenes/analysis-report.md +66 -0
  144. package/skills/lark-base-database/references/design/scenes/brand-showcase.md +70 -0
  145. package/skills/lark-base-database/references/design/scenes/business-operations.md +85 -0
  146. package/skills/lark-base-database/references/design/scenes/campaign-report.md +66 -0
  147. package/skills/lark-base-database/references/design/scenes/dashboard.md +77 -0
  148. package/skills/lark-base-database/references/design/scenes/festive-campaign.md +69 -0
  149. package/skills/lark-base-database/references/design/theme.md +27 -0
  150. package/skills/lark-base-database/references/design/typography.md +56 -0
  151. package/skills/lark-base-database/references/development.md +165 -32
  152. package/skills/lark-base-database/references/environment-variables.md +55 -0
  153. package/skills/lark-base-database/references/faas.md +41 -48
  154. package/skills/lark-base-database/references/file-transfer.md +25 -25
  155. package/skills/lark-base-database/references/frontend-design-guide.md +33 -77
  156. package/skills/lark-base-database/references/mutations.md +295 -0
  157. package/skills/lark-base-database/references/permission-design.md +24 -24
  158. package/skills/lark-base-database/references/release.md +51 -24
  159. package/skills/lark-base-database/references/style-guide.md +10 -67
  160. package/skills/lark-base-database/references/verification.md +15 -11
  161. package/skills/lark-base-database/references/workspace.md +46 -8
  162. package/templates/database-starter/README.md +34 -4
  163. package/templates/database-starter/database-functions/README.md +8 -0
  164. package/templates/database-starter/mutations/README.md +19 -0
  165. package/templates/database-starter/page/README.md +25 -19
  166. package/templates/database-starter/page/package-lock.json +8 -16
  167. package/templates/database-starter/page/package.json +2 -3
  168. package/templates/database-starter/page/src/App.tsx +23 -3
  169. package/templates/database-starter/page/src/components/UserSelectorEditor/index.tsx +32 -4
  170. package/templates/database-starter/page/src/components/UserSelectorPanel/index.tsx +104 -34
  171. package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.test.ts +55 -11
  172. package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.ts +68 -25
  173. package/templates/database-starter/page/src/components/database-runtime-ready.tsx +1 -1
  174. package/templates/database-starter/page/src/components/database-user-menu/index.tsx +4 -7
  175. package/templates/database-starter/page/src/components/feedback/page-state.tsx +40 -5
  176. package/templates/database-starter/page/src/components/floating-button/assets/edit-active.svg +3 -0
  177. package/templates/database-starter/page/src/components/floating-button/assets/edit-default.svg +3 -0
  178. package/templates/database-starter/page/src/components/floating-button/index.tsx +75 -112
  179. package/templates/database-starter/page/src/components/floating-button/utils/project-meta.test.ts +107 -37
  180. package/templates/database-starter/page/src/components/floating-button/utils/project-meta.ts +13 -50
  181. package/templates/database-starter/page/src/components/ui-language.test.tsx +101 -0
  182. package/templates/database-starter/page/src/examples/ai-function/ImageTaskOperation.tsx +202 -0
  183. package/templates/database-starter/page/src/examples/ai-function/README.md +14 -0
  184. package/templates/database-starter/page/src/examples/ai-function/TextStreamOperation.tsx +184 -0
  185. package/templates/database-starter/page/src/examples/ai-function/ai-copy.ts +173 -0
  186. package/templates/database-starter/page/src/examples/ai-function/ai-delta.test.ts +30 -0
  187. package/templates/database-starter/page/src/examples/ai-function/ai-delta.ts +26 -0
  188. package/templates/database-starter/page/src/examples/ai-function/ai-language.test.tsx +86 -0
  189. package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.test.ts +201 -0
  190. package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.tsx +382 -0
  191. package/templates/database-starter/page/src/lib/attachment-image.tsx +144 -0
  192. package/templates/database-starter/page/src/lib/database-function-inputs.generated.ts +4 -0
  193. package/templates/database-starter/page/src/lib/database-runtime-attachment.test.ts +180 -76
  194. package/templates/database-starter/page/src/lib/database-runtime.test.ts +130 -10
  195. package/templates/database-starter/page/src/lib/database-runtime.ts +484 -51
  196. package/templates/database-starter/page/src/lib/date-time.ts +61 -0
  197. package/templates/database-starter/page/src/lib/ui-language.ts +86 -0
  198. package/templates/database-starter/page/src/main.tsx +1 -1
  199. package/templates/database-starter/page/tsconfig.json +2 -2
  200. package/templates/database-starter/page/vite.config.ts +1 -2
  201. package/templates/database-starter/prettierignore +1 -1
  202. package/templates/database-starter/server-functions/README.md +2 -2
  203. package/templates/database-starter/server-functions/package-lock.json +4 -4
  204. package/templates/database-starter/server-functions/package.json +1 -1
  205. package/dist/agent-guide.d.ts +0 -19
  206. package/dist/agent-guide.d.ts.map +0 -1
  207. package/dist/agent-guide.js +0 -31
  208. package/dist/agent-guide.js.map +0 -1
  209. package/dist/database-skill.d.ts +0 -24
  210. package/dist/database-skill.d.ts.map +0 -1
  211. package/dist/database-skill.js +0 -148
  212. package/dist/database-skill.js.map +0 -1
  213. package/skills/lark-base-database/references/base-sql-unsupported.md +0 -42
  214. package/skills/lark-base-database/references/database-capability-skill.md +0 -40
  215. package/skills/lark-base-database/references/sql-function.md +0 -289
  216. package/templates/database-capability/SKILL.md.tmpl +0 -58
  217. package/templates/database-starter/page/src/components/floating-button/chatbot-entry.test.ts +0 -23
  218. package/templates/database-starter/page/src/components/floating-button/chatbot-entry.tsx +0 -873
  219. package/templates/database-starter/page/src/components/floating-button/local-chat-panel.test.ts +0 -25
  220. package/templates/database-starter/page/src/components/floating-button/local-chat-panel.tsx +0 -197
  221. package/templates/database-starter/page/src/components/floating-button/utils/env.ts +0 -18
  222. package/templates/database-starter/sql/README.md +0 -8
@@ -1,9 +1,41 @@
1
1
  import {
2
+ callSiteAIFunction as callRuntimeAIFunction,
3
+ createSiteAIFunctionTask as createRuntimeAIFunctionTask,
4
+ getSiteAIFunctionTask as getRuntimeAIFunctionTask,
5
+ invokeSiteAIFunctionStream as invokeRuntimeAIFunctionStream,
2
6
  invokeHttp,
3
7
  siteRuntime,
4
8
  fetch as siteRuntimeFetch,
9
+ type CallSiteAIFunctionParams as RuntimeCallSiteAIFunctionParams,
10
+ type InvokeSiteAIFunctionStreamParams as RuntimeInvokeSiteAIFunctionStreamParams,
11
+ type SiteAIFunctionResult,
12
+ type SiteAIFunctionStreamHandle,
13
+ type SiteAIStreamDelta,
14
+ type SiteAIImageAsset,
15
+ type SiteAIImageGenerationInput,
16
+ type SiteAIVideoTask,
5
17
  type SiteProjectEnv,
6
18
  } from '@lark-base-open/base-site-channel/runtime'
19
+ import type { DatabaseFunctionInputMap } from './database-function-inputs.generated'
20
+
21
+ /**
22
+ * alpha.17 的运行时代码已经会把 responseType 透传给宿主,但其公开 d.ts 仍停留在文本请求,
23
+ * 且没有声明顶层 signal。这里局部扩宽这一个二进制请求,避免生成工程为了通过 tsc 删除真实
24
+ * 运行所需字段;SDK 声明补齐后可以直接移除此兼容层。
25
+ */
26
+ type BinarySiteHttpRequest = {
27
+ init: {
28
+ method: 'GET'
29
+ responseType: 'arrayBuffer'
30
+ timeoutMs: number
31
+ }
32
+ signal?: AbortSignal
33
+ url: string
34
+ }
35
+
36
+ const invokeBinaryHttp = invokeHttp as unknown as (
37
+ request: BinarySiteHttpRequest,
38
+ ) => Promise<Response>
7
39
 
8
40
  /** 内置 Runtime Function(function.runtime.*,由宿主固定分发)。
9
41
  * 随 channel 包发布,直接透传官方实现,不在 starter 侧包装。 */
@@ -21,7 +53,7 @@ export interface DatabaseContext {
21
53
  token: string
22
54
  }
23
55
 
24
- /** 附件在底表里的位置。四个值都来自 catalog.json 与 SQL 结果。 */
56
+ /** 附件在底表里的位置。四个值都来自 catalog.json 与 Database Function 结果。 */
25
57
  export interface DatabaseAttachmentRef {
26
58
  tableId: string
27
59
  fieldId: string
@@ -32,6 +64,71 @@ export interface DatabaseAttachmentRef {
32
64
  /** 产物环境沿用 site.v1 协议的规范类型,避免两侧各自声明后漂移。 */
33
65
  export type DatabaseEnv = SiteProjectEnv
34
66
 
67
+ export type SiteAIMessageContentPart =
68
+ | { text: string; type: 'text' }
69
+ | { detail?: 'high' | 'low' | 'xhigh'; type: 'image'; url: string }
70
+ | { fps?: number; type: 'video'; url: string }
71
+ | { type: 'audio'; url: string }
72
+ | { filename?: string; type: 'file'; url: string }
73
+
74
+ /** AI Function 对话消息。所有 content 都使用非空 parts 数组;System Prompt 由 Function 配置管理。
75
+ * 元组式 `[X, ...X[]]` 保证空数组无法通过 TS 编译,避免把空 content 推迟到运行态服务端校验。 */
76
+ export type SiteAIMessage =
77
+ | {
78
+ content: [SiteAIMessageContentPart, ...SiteAIMessageContentPart[]]
79
+ role: 'user'
80
+ }
81
+ | {
82
+ content: [
83
+ Extract<SiteAIMessageContentPart, { type: 'text' }>,
84
+ ...Array<Extract<SiteAIMessageContentPart, { type: 'text' }>>,
85
+ ]
86
+ role: 'assistant'
87
+ }
88
+
89
+ type WithSiteAIMessages<T> = T extends unknown
90
+ ? Omit<T, 'messages'> & { messages?: SiteAIMessage[] }
91
+ : never
92
+
93
+ export type CallSiteAIFunctionParams =
94
+ WithSiteAIMessages<RuntimeCallSiteAIFunctionParams>
95
+
96
+ export type InvokeSiteAIFunctionStreamParams = Omit<
97
+ RuntimeInvokeSiteAIFunctionStreamParams,
98
+ 'messages'
99
+ > & { messages: SiteAIMessage[] }
100
+
101
+ /** 单帧增量与可取消的流句柄。 */
102
+ export type { SiteAIStreamDelta, SiteAIFunctionStreamHandle }
103
+
104
+ /** AI Function 可判别联合返回与媒体任务态,供页面按 type 分支渲染。 */
105
+ export type SiteAIMediaTask =
106
+ | SiteAIVideoTask
107
+ | {
108
+ capabilityType?: 'image'
109
+ errCode?: string
110
+ errMessage?: string
111
+ imageAssets?: SiteAIImageAsset[]
112
+ progress?: number
113
+ requestId: string
114
+ status: 'cancelled' | 'failed' | 'queued' | 'running' | 'succeeded'
115
+ taskId: string
116
+ type: 'image_task'
117
+ }
118
+ export type {
119
+ SiteAIFunctionResult,
120
+ SiteAIImageGenerationInput,
121
+ SiteAIVideoTask,
122
+ }
123
+
124
+ const MEDIA_TASK_STATUSES = new Set([
125
+ 'cancelled',
126
+ 'failed',
127
+ 'queued',
128
+ 'running',
129
+ 'succeeded',
130
+ ])
131
+
35
132
  /** Function 调用的统一响应包裹结构,与运行态接口一致。 */
36
133
  export interface DatabaseFunctionResponse<Data = unknown> {
37
134
  code: number
@@ -46,10 +143,8 @@ const ARTIFACT_PATH_PATTERN = /^\/?(preview\/)?app\/([^/]+)(\/|$)/
46
143
  const FUNCTION_ID_PATTERN = /^[A-Za-z0-9_-]+$/
47
144
 
48
145
  const DATABASE_RUNTIME_GATEWAY_PREFIX = '/space/api/bitable/site/v1'
49
- const ATTACHMENT_COVER_PATH = '/space/api/box/stream/download/v2/cover/'
50
146
  const ATTACHMENT_DOWNLOAD_URL_PATH =
51
147
  '/space/api/box/file/temporary_download_url'
52
- const ATTACHMENT_COVER_SIZES = [120, 360, 720, 1280] as const
53
148
  const PREVIEW_ENV: DatabaseEnv = 'preview'
54
149
 
55
150
  let databaseContextPromise: Promise<DatabaseContext> | null = null
@@ -87,6 +182,242 @@ function inferGatewayOrigin(): string {
87
182
  }
88
183
  }
89
184
 
185
+ /** 按 functionKey 调用 AI Function,返回可判别联合结果(text/image_task/video_task)。
186
+ *
187
+ * 页面用 switch(result.type) 分支渲染:text 为聚合文本,image_task/video_task
188
+ * 需再用 getSiteAIFunctionTask 轮询。传 messages 走文本/多模态理解,
189
+ * 传 input 创建媒体任务;随后用 getSiteAIFunctionTask 轮询。
190
+ */
191
+ export async function callSiteAIFunction(
192
+ params: CallSiteAIFunctionParams,
193
+ ): Promise<SiteAIFunctionResult> {
194
+ const result = await callRuntimeAIFunction(
195
+ params as unknown as RuntimeCallSiteAIFunctionParams,
196
+ )
197
+ if (params.input && !params.messages) {
198
+ return normalizeSiteAIMediaTask(result)
199
+ }
200
+ return result
201
+ }
202
+
203
+ /** 显式流式调用 AI Function(text/vision):逐帧消费增量,卸载或取消时 destroy。 */
204
+ export function invokeSiteAIFunctionStream(
205
+ params: InvokeSiteAIFunctionStreamParams,
206
+ ): SiteAIFunctionStreamHandle {
207
+ return invokeRuntimeAIFunctionStream(
208
+ params as unknown as RuntimeInvokeSiteAIFunctionStreamParams,
209
+ )
210
+ }
211
+
212
+ function readString(
213
+ source: Record<string, unknown>,
214
+ names: string[],
215
+ ): string | undefined {
216
+ for (const name of names) {
217
+ const value = source[name]
218
+ if (typeof value === 'string' && value.trim()) {
219
+ return value
220
+ }
221
+ }
222
+ return undefined
223
+ }
224
+
225
+ function readNumber(
226
+ source: Record<string, unknown>,
227
+ names: string[],
228
+ ): number | undefined {
229
+ for (const name of names) {
230
+ const value = source[name]
231
+ if (typeof value === 'number' && Number.isFinite(value)) {
232
+ return value
233
+ }
234
+ }
235
+ return undefined
236
+ }
237
+
238
+ function readCapabilityType(
239
+ source: Record<string, unknown>,
240
+ ): SiteAIMediaTask['capabilityType'] {
241
+ const value = readString(source, [
242
+ 'capabilityType',
243
+ 'capability_type',
244
+ 'CapabilityType',
245
+ ])
246
+ return value === 'image' || value === 'video' ? value : undefined
247
+ }
248
+
249
+ function normalizeImageAssets(value: unknown): SiteAIImageAsset[] | undefined {
250
+ if (!Array.isArray(value)) {
251
+ return undefined
252
+ }
253
+ return value.filter(isRecord).map((asset) => {
254
+ const size = readString(asset, ['size', 'Size'])
255
+ const token = readString(asset, ['token', 'Token'])
256
+ const url = readString(asset, ['url', 'URL'])
257
+ return {
258
+ safetyStatus:
259
+ readString(asset, ['safetyStatus', 'safety_status', 'SafetyStatus']) ??
260
+ '',
261
+ ...(size ? { size } : {}),
262
+ ...(token ? { token } : {}),
263
+ ...(url ? { url } : {}),
264
+ }
265
+ })
266
+ }
267
+
268
+ function normalizeVideoAsset(value: unknown): SiteAIVideoTask['videoAsset'] {
269
+ if (!isRecord(value)) {
270
+ return undefined
271
+ }
272
+ const duration =
273
+ value.durationSeconds ?? value.duration_seconds ?? value.DurationSeconds
274
+ const token = readString(value, ['token', 'Token'])
275
+ const url = readString(value, ['url', 'URL'])
276
+ return {
277
+ ...(typeof duration === 'number' ? { durationSeconds: duration } : {}),
278
+ ...(token ? { token } : {}),
279
+ ...(url ? { url } : {}),
280
+ }
281
+ }
282
+
283
+ function mediaTaskError(
284
+ message: string,
285
+ code = 'INVALID_AI_FUNCTION_TASK',
286
+ ): Error {
287
+ const error = new Error(message) as Error & { code: string }
288
+ error.name = 'SiteAIFunctionTaskError'
289
+ error.code = code
290
+ return error
291
+ }
292
+
293
+ /** 校验并归一化 AI Function 媒体任务响应。
294
+ *
295
+ * Channel SDK 新旧版本、OpenAPI 和父页面代理可能返回裸 task 或
296
+ * `{code,msg,data}` 包裹;页面只接受含合法 taskId/type/status 的任务。
297
+ */
298
+ export function normalizeSiteAIMediaTask(value: unknown): SiteAIMediaTask {
299
+ if (!isRecord(value)) {
300
+ throw mediaTaskError('AI Function task response has an invalid structure')
301
+ }
302
+
303
+ const envelopeError = readEnvelopeError(value)
304
+ if (envelopeError) {
305
+ throw mediaTaskError(envelopeError.message, envelopeError.code)
306
+ }
307
+ if ('data' in value && value.data !== undefined) {
308
+ return normalizeSiteAIMediaTask(value.data)
309
+ }
310
+
311
+ const type = readString(value, ['type', 'Type'])
312
+ const taskId = readString(value, ['taskId', 'task_id', 'TaskID'])
313
+ const status = value.status ?? value.Status
314
+ if (
315
+ (type !== 'image_task' && type !== 'video_task') ||
316
+ !taskId ||
317
+ typeof status !== 'string' ||
318
+ !MEDIA_TASK_STATUSES.has(status)
319
+ ) {
320
+ throw mediaTaskError('AI Function task response has an invalid structure')
321
+ }
322
+
323
+ const imageAssets = normalizeImageAssets(
324
+ value.imageAssets ?? value.image_assets ?? value.ImageAssets,
325
+ )
326
+ const videoAsset = normalizeVideoAsset(
327
+ value.videoAsset ?? value.video_asset ?? value.VideoAsset,
328
+ )
329
+ const progress = readNumber(value, ['progress', 'Progress'])
330
+ const capabilityType = readCapabilityType(value)
331
+ const errCode = readString(value, ['errCode', 'err_code', 'ErrCode'])
332
+ const errMessage = readString(value, [
333
+ 'errMessage',
334
+ 'err_message',
335
+ 'ErrMessage',
336
+ ])
337
+ return {
338
+ ...(capabilityType ? { capabilityType } : {}),
339
+ ...(errCode ? { errCode } : {}),
340
+ ...(errMessage ? { errMessage } : {}),
341
+ ...(imageAssets ? { imageAssets } : {}),
342
+ ...(progress !== undefined ? { progress } : {}),
343
+ ...(videoAsset ? { videoAsset } : {}),
344
+ requestId:
345
+ readString(value, ['requestId', 'request_id', 'RequestID']) ?? '',
346
+ status: status as SiteAIMediaTask['status'],
347
+ taskId,
348
+ type,
349
+ } as SiteAIMediaTask
350
+ }
351
+
352
+ function readEnvelopeError(
353
+ value: Record<string, unknown>,
354
+ ): { code: string; message: string } | undefined {
355
+ const directCode = value.code ?? value.Code
356
+ if (
357
+ (typeof directCode === 'number' && directCode !== 0) ||
358
+ (typeof directCode === 'string' && directCode && directCode !== '0')
359
+ ) {
360
+ return {
361
+ code: String(directCode),
362
+ message:
363
+ readString(value, ['msg', 'message', 'Message']) ??
364
+ 'Failed to create the AI Function task',
365
+ }
366
+ }
367
+
368
+ const baseResp = value.BaseResp ?? value.base_resp ?? value.baseResp
369
+ if (!isRecord(baseResp)) {
370
+ return undefined
371
+ }
372
+
373
+ const statusCode =
374
+ baseResp.StatusCode ?? baseResp.status_code ?? baseResp.statusCode
375
+ if (
376
+ (typeof statusCode === 'number' && statusCode !== 0) ||
377
+ (typeof statusCode === 'string' && statusCode && statusCode !== '0')
378
+ ) {
379
+ return {
380
+ code: String(statusCode),
381
+ message:
382
+ readString(baseResp, [
383
+ 'StatusMessage',
384
+ 'status_message',
385
+ 'statusMessage',
386
+ ]) ?? 'Failed to create the AI Function task',
387
+ }
388
+ }
389
+ return undefined
390
+ }
391
+
392
+ export function hasRunnableMediaTask(
393
+ task: SiteAIMediaTask,
394
+ ): task is SiteAIMediaTask & { taskId: string } {
395
+ return (
396
+ Boolean(task.taskId.trim()) &&
397
+ (task.status === 'queued' || task.status === 'running')
398
+ )
399
+ }
400
+
401
+ /** 提交文生图或文生视频任务,返回封装后的 taskId 与初始状态。 */
402
+ export async function createSiteAIFunctionTask(
403
+ params: CallSiteAIFunctionParams,
404
+ ): Promise<SiteAIMediaTask> {
405
+ return normalizeSiteAIMediaTask(await createRuntimeAIFunctionTask(params))
406
+ }
407
+
408
+ /** 轮询文生图或文生视频任务进度与最终资源。 */
409
+ export async function getSiteAIFunctionTask(
410
+ functionKey: string,
411
+ taskId: string,
412
+ ): Promise<SiteAIMediaTask> {
413
+ if (!taskId.trim()) {
414
+ throw mediaTaskError('AI Function task ID must not be empty')
415
+ }
416
+ return normalizeSiteAIMediaTask(
417
+ await getRuntimeAIFunctionTask(functionKey, taskId),
418
+ )
419
+ }
420
+
90
421
  /** 从产物托管路径推断当前应用 token 与运行环境。
91
422
  *
92
423
  * 这是宿主未下发上下文时的兜底:BaseApp 形态下 site 与 BaseApp 共用同一个 token,
@@ -172,7 +503,10 @@ export async function resolveDatabaseContext(): Promise<DatabaseContext> {
172
503
  host = readHostDatabaseContext(await siteRuntime.getRuntimeContext())
173
504
  } catch (error) {
174
505
  // 宿主未实现或握手尚未完成时退回路径推断,不阻断业务。
175
- console.warn('读取宿主 Runtime 上下文失败,回退产物路径推断', error)
506
+ console.warn(
507
+ 'Could not read the host Runtime context; inferring it from the artifact path',
508
+ error,
509
+ )
176
510
  }
177
511
 
178
512
  const context: DatabaseContext = {
@@ -183,7 +517,7 @@ export async function resolveDatabaseContext(): Promise<DatabaseContext> {
183
517
 
184
518
  if (!context.token) {
185
519
  console.error(
186
- '未能确定应用 token:宿主未下发 siteToken,且当前路径不符合产物托管规则',
520
+ 'Cannot determine the app token: the host did not provide siteToken and the current path does not match an artifact path',
187
521
  )
188
522
  }
189
523
 
@@ -206,7 +540,6 @@ function hasCompleteRef(ref: DatabaseAttachmentRef): boolean {
206
540
  )
207
541
  }
208
542
 
209
- /** baseAppToken 把附件鉴权绑定到应用;省略会退回底表权限判定并导致访客被拒。 */
210
543
  function buildAttachmentExtra(
211
544
  context: DatabaseContext,
212
545
  ref: DatabaseAttachmentRef,
@@ -217,40 +550,76 @@ function buildAttachmentExtra(
217
550
  attachments: {
218
551
  [ref.fieldId]: { [ref.recordId]: [ref.fileToken] },
219
552
  },
220
- baseAppToken: context.token,
553
+ siteToken: context.token,
554
+ siteEnv: context.env,
221
555
  },
222
556
  })
223
557
  }
224
558
 
225
- /** 封面由浏览器直连网关获取,必须是绝对 URL;尺寸向上匹配服务端档位。
226
- * 构造 URL 不会检查附件是否挂载成功,不能用于确认写入结果。
227
- *
228
- * @returns 可直接交给 img.src 的地址;上下文或附件位置不完整时返回空字符串。
559
+ function attachmentRequestError(operation: string, cause: unknown): Error {
560
+ // Errors from the SDK can carry actionable host guidance and a distinct type.
561
+ if (cause instanceof Error) return cause
562
+ const rawCode = isRecord(cause) ? cause.code : undefined
563
+ const code =
564
+ typeof rawCode === 'string' || typeof rawCode === 'number'
565
+ ? String(rawCode)
566
+ : ''
567
+ const summary =
568
+ code === 'REQUEST_TIMEOUT' || code === '10005'
569
+ ? `${operation} timed out`
570
+ : `${operation} failed`
571
+ return Object.assign(
572
+ new Error(`${summary}${code ? ` (code ${code})` : ''}`),
573
+ {
574
+ cause,
575
+ ...(code ? { code } : {}),
576
+ },
577
+ )
578
+ }
579
+
580
+ /** 宿主用当前访问者身份同源取图,返回可创建本地 URL 的图片字节。
581
+ * 调用方在替换或卸载时取消请求并释放自己创建的 Blob URL。
229
582
  */
230
- export function buildAttachmentCoverUrl(
231
- context: DatabaseContext,
583
+ export async function fetchAttachmentCover(
232
584
  ref: DatabaseAttachmentRef,
233
585
  size = 360,
234
- ): string {
235
- if (!context.gatewayOrigin || !context.token || !hasCompleteRef(ref)) {
236
- return ''
586
+ options: { signal?: AbortSignal } = {},
587
+ ): Promise<Blob> {
588
+ options.signal?.throwIfAborted()
589
+ if (!hasCompleteRef(ref) || !Number.isFinite(size) || size <= 0) {
590
+ throw new Error('Attachment location or image size is invalid')
237
591
  }
238
-
239
- const coverSize =
240
- ATTACHMENT_COVER_SIZES.find((candidate) => candidate >= size) ??
241
- ATTACHMENT_COVER_SIZES[ATTACHMENT_COVER_SIZES.length - 1]
242
- const searchParams = new URLSearchParams({
592
+ const context = await resolveDatabaseContext()
593
+ options.signal?.throwIfAborted()
594
+ if (!context.token)
595
+ throw new Error('Cannot load the attachment image without an app token')
596
+ const matchedSize =
597
+ [120, 360, 720, 1280].find((candidate) => candidate >= size) ?? 1280
598
+ const query = new URLSearchParams({
243
599
  extra: buildAttachmentExtra(context, ref),
244
- width: String(coverSize),
245
- height: String(coverSize),
600
+ width: String(matchedSize),
601
+ height: String(matchedSize),
246
602
  policy: 'allow_up',
247
603
  fallback_source: '1',
248
604
  })
249
-
250
- return (
251
- `${context.gatewayOrigin}${ATTACHMENT_COVER_PATH}` +
252
- `${encodeURIComponent(ref.fileToken)}?${searchParams.toString()}`
253
- )
605
+ const response = await invokeBinaryHttp({
606
+ url: `/space/api/box/stream/download/v2/cover/${encodeURIComponent(ref.fileToken)}?${query}`,
607
+ init: { method: 'GET', responseType: 'arrayBuffer', timeoutMs: 45_000 },
608
+ signal: options.signal,
609
+ }).catch((cause: unknown) => {
610
+ throw attachmentRequestError('Attachment image request', cause)
611
+ })
612
+ if (!response.ok) {
613
+ const logId = response.headers.get('x-tt-logid')
614
+ throw new Error(
615
+ `Failed to load attachment image: HTTP ${response.status}${logId ? `, LogID ${logId}` : ''}`,
616
+ )
617
+ }
618
+ const blob = await response.blob()
619
+ if (!blob.size || !blob.type.startsWith('image/')) {
620
+ throw new Error('The host did not return a valid image')
621
+ }
622
+ return blob
254
623
  }
255
624
 
256
625
  /** 经父页面 bridge 取短时效自证链接;原文件由浏览器下载,不进入页面 JavaScript。
@@ -263,11 +632,15 @@ export async function fetchAttachmentDownloadUrl(
263
632
  ref: DatabaseAttachmentRef,
264
633
  ): Promise<string> {
265
634
  if (!context.token) {
266
- throw new Error('应用 token 未确定,无法获取附件下载链接')
635
+ throw new Error(
636
+ 'Cannot get an attachment download URL without an app token',
637
+ )
267
638
  }
268
639
 
269
640
  if (!hasCompleteRef(ref)) {
270
- throw new Error('附件位置不完整,无法获取下载链接')
641
+ throw new Error(
642
+ 'Cannot get an attachment download URL: the attachment location is incomplete',
643
+ )
271
644
  }
272
645
 
273
646
  const response = await siteRuntimeFetch(ATTACHMENT_DOWNLOAD_URL_PATH, {
@@ -277,20 +650,48 @@ export async function fetchAttachmentDownloadUrl(
277
650
  }),
278
651
  headers: { 'content-type': 'application/json' },
279
652
  method: 'POST',
653
+ }).catch((cause: unknown) => {
654
+ throw attachmentRequestError('Attachment download URL request', cause)
280
655
  })
281
656
 
657
+ const logId = response.headers?.get('x-tt-logid')?.trim()
658
+ let body: unknown
659
+ try {
660
+ body = await response.json()
661
+ } catch {
662
+ throw new Error(
663
+ response.ok
664
+ ? `Attachment download URL response is not valid JSON${logId ? `, LogID ${logId}` : ''}`
665
+ : `Failed to get attachment download URL: HTTP ${response.status}${logId ? `, LogID ${logId}` : ''}`,
666
+ )
667
+ }
668
+
669
+ const code = isRecord(body) ? body.code : undefined
670
+ if (
671
+ (typeof code === 'number' && code !== 0) ||
672
+ (typeof code === 'string' && code.trim() !== '' && code.trim() !== '0')
673
+ ) {
674
+ const error = new Error(
675
+ `Attachment service could not provide a download URL (code ${String(code)}${logId ? `, LogID ${logId}` : ''})`,
676
+ )
677
+ throw Object.assign(error, { code: String(code), cause: body })
678
+ }
679
+
282
680
  if (!response.ok) {
283
- throw new Error(`附件下载链接获取失败:HTTP ${response.status}`)
681
+ throw new Error(
682
+ `Failed to get attachment download URL: HTTP ${response.status}${logId ? `, LogID ${logId}` : ''}`,
683
+ )
284
684
  }
285
685
 
286
- const body: unknown = await response.json()
287
686
  const url =
288
687
  isRecord(body) && isRecord(body.data) && typeof body.data.url === 'string'
289
688
  ? body.data.url.trim()
290
689
  : ''
291
690
 
292
691
  if (!url) {
293
- throw new Error('附件下载链接获取失败:响应缺少 data.url')
692
+ throw new Error(
693
+ `Attachment download URL response is missing data.url${logId ? `, LogID ${logId}` : ''}`,
694
+ )
294
695
  }
295
696
 
296
697
  return url
@@ -299,19 +700,11 @@ export async function fetchAttachmentDownloadUrl(
299
700
  /** 构造运行态读取应用信息的统一网关地址。
300
701
  *
301
702
  * @param context 当前应用上下文。
302
- * @param withChatbot 是否附带项目绑定的 chatbot 列表。
303
703
  * @returns GetInvokeProject 的同源相对路径。
304
704
  */
305
- export function buildDatabaseInvokeGetPath(
306
- context: DatabaseContext,
307
- withChatbot: boolean,
308
- ): string {
705
+ export function buildDatabaseInvokeGetPath(context: DatabaseContext): string {
309
706
  const searchParams = new URLSearchParams()
310
707
 
311
- if (withChatbot) {
312
- searchParams.set('withChatbot', 'true')
313
- }
314
-
315
708
  if (context.env === PREVIEW_ENV) {
316
709
  searchParams.set('env', PREVIEW_ENV)
317
710
  }
@@ -337,7 +730,7 @@ export function buildDatabaseInvokeFunctionPath(
337
730
  ): string {
338
731
  if (!FUNCTION_ID_PATTERN.test(functionId)) {
339
732
  throw new TypeError(
340
- `functionId ${functionId} 不满足命名约束 ${String(FUNCTION_ID_PATTERN)}`,
733
+ `functionId ${functionId} does not match the required pattern ${String(FUNCTION_ID_PATTERN)}`,
341
734
  )
342
735
  }
343
736
 
@@ -353,23 +746,31 @@ export function buildDatabaseInvokeFunctionPath(
353
746
  /** 调用应用 Function。
354
747
  *
355
748
  * 请求经父页面同源代理发往统一网关;整个请求体即 payload,业务参数包在 params 下。
356
- * 目标环境只走 query 参数 env:服务端 resolveProjectEnv 显式入参优先,且
357
- * cloneBaseAppHeaders 会剥掉入站的 x-project-env 后按归一化 env 重写,
358
- * 再带同名 Header 属于重复下发。
749
+ * 目标环境由 query 参数 env 指定,不重复添加环境 Header。
359
750
  * 4xx/5xx 直接抛错,业务码由调用方按 operation 契约校验一次顶层 code。
360
751
  *
361
752
  * @param functionId Base Database Function ID。
362
- * @param params 函数入参:SQL Function 用数组,FaaS 用业务对象。
753
+ * @param params 函数入参:Database Function 用数组,FaaS 用业务对象。
363
754
  * @returns 统一包裹的函数响应。
364
755
  */
756
+ export type DatabaseFunctionInvocation<Inputs extends object> = [
757
+ Extract<keyof Inputs, string>,
758
+ ] extends [never]
759
+ ? [functionId: never, params: never]
760
+ : {
761
+ [FunctionId in Extract<keyof Inputs, string>]: [
762
+ functionId: FunctionId,
763
+ params: Inputs[FunctionId],
764
+ ]
765
+ }[Extract<keyof Inputs, string>]
766
+
365
767
  export async function invokeDatabaseFunction<Data = unknown>(
366
- functionId: string,
367
- params?: unknown,
768
+ ...[functionId, params]: DatabaseFunctionInvocation<DatabaseFunctionInputMap>
368
769
  ): Promise<DatabaseFunctionResponse<Data>> {
369
770
  const context = await resolveDatabaseContext()
370
771
 
371
772
  if (!context.token) {
372
- throw new Error(`应用 token 未确定,无法调用 Function ${functionId}`)
773
+ throw new Error(`Cannot invoke Function ${functionId} without an app token`)
373
774
  }
374
775
 
375
776
  const response = await invokeHttp({
@@ -382,8 +783,40 @@ export async function invokeDatabaseFunction<Data = unknown>(
382
783
  })
383
784
 
384
785
  if (!response.ok) {
385
- throw new Error(`Function ${functionId} 调用失败:HTTP ${response.status}`)
786
+ throw new Error(
787
+ `Function ${functionId} request failed: HTTP ${response.status}`,
788
+ )
386
789
  }
387
790
 
388
791
  return (await response.json()) as DatabaseFunctionResponse<Data>
389
792
  }
793
+
794
+ /** 每组参数应用同一份已发布 Mutation 声明;总记录数最多 200。 */
795
+ export async function invokeDatabaseMutationBatch<Data = unknown>(
796
+ functionId: string,
797
+ parameterGroups: unknown[][],
798
+ ): Promise<DatabaseFunctionResponse<Data>> {
799
+ if (
800
+ !Array.isArray(parameterGroups) ||
801
+ parameterGroups.length < 1 ||
802
+ parameterGroups.length > 200 ||
803
+ parameterGroups.some((params) => !Array.isArray(params))
804
+ ) {
805
+ throw new TypeError('Mutation batch requires 1 to 200 parameter arrays')
806
+ }
807
+ const context = await resolveDatabaseContext()
808
+ if (!context.token) throw new Error('应用 token 未确定,无法调用 Mutation')
809
+ const response = await invokeHttp({
810
+ url: buildDatabaseInvokeFunctionPath(context, functionId),
811
+ init: {
812
+ body: JSON.stringify({
813
+ batch: parameterGroups.map((params) => ({ params })),
814
+ }),
815
+ headers: { 'content-type': 'application/json' },
816
+ method: 'POST',
817
+ },
818
+ })
819
+ if (!response.ok)
820
+ throw new Error(`Mutation 调用失败:HTTP ${response.status}`)
821
+ return (await response.json()) as DatabaseFunctionResponse<Data>
822
+ }