@h-ai/ai 0.1.0-alpha.50 → 0.1.0-alpha.53

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 CHANGED
@@ -316,7 +316,7 @@ return playable.data
316
316
 
317
317
  浏览器 / 移动端通过 `@h-ai/serv` 暴露的统一语音 WebSocket 入口访问,`@h-ai/ai/client` 提供与 Node 端一致的 `audio.*` API(传输细节内部隐藏)。浏览器客户端严格区分正常结束、取消(`AUDIO_CANCELLED`)与异常断连(`AUDIO_CONNECTION_FAILED`):取消或在 `end` 前断连会抛出对应领域错误码,`synthesize` 不会把未完成的部分音频当作成功结果返回。
318
318
 
319
- > 自托管模型服务(faster-whisper / IndexTTS / Qwen3-4B 的 CPU/GPU Docker 镜像、权重下载与离线打包)见 [`models/`](./models/README.md)。镜像与权重下载优先使用 ModelScope(中国网络友好),自动回退 HuggingFace 镜像。
319
+ > 自托管模型服务(faster-whisper / IndexTTS / Qwen3-4B 的 CPU/GPU Docker 镜像、权重下载与离线打包)见 [`models/`](./models/README.md)。镜像与权重下载优先使用 ModelScope,自动回退 HuggingFace 镜像。
320
320
 
321
321
  ### 文生图(Image)
322
322
 
package/dist/index.js CHANGED
@@ -9,10 +9,10 @@ import { DefaultExecutionEventBusManager, DefaultRequestHandler, JsonRpcTranspor
9
9
  import { Buffer } from 'buffer';
10
10
  import { gunzipSync } from 'zlib';
11
11
  import WebSocket from 'ws';
12
+ import process2 from 'process';
12
13
  import OpenAI3, { toFile } from 'openai';
13
14
  import { nanoid } from 'nanoid';
14
15
  import { z } from 'zod';
15
- import process from 'process';
16
16
  import { reldb } from '@h-ai/reldb';
17
17
  import { vecdb } from '@h-ai/vecdb';
18
18
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
@@ -873,16 +873,23 @@ function createIndexTtsAudioProvider() {
873
873
  const headers = {};
874
874
  if (model.apiKey)
875
875
  headers.Authorization = `Bearer ${model.apiKey}`;
876
- const response = await fetch(`${model.baseUrl.replace(/\/$/, "")}/audio/speech`, {
877
- method: "POST",
878
- headers,
879
- body: form,
880
- signal: combineSignal(signal, model.timeout)
881
- });
882
- if (!response.ok)
883
- return err(HaiAIError.AUDIO_UPSTREAM_ERROR, aiM("ai_audioUpstreamError", { params: { error: await describeHttpError(response) } }));
884
- const data = new Uint8Array(await response.arrayBuffer());
885
- return ok(buildSynthesisResult(data, out, response.headers));
876
+ const dispatcher = await createNodeDispatcher(model.timeout);
877
+ try {
878
+ const requestInit = {
879
+ method: "POST",
880
+ headers,
881
+ body: form,
882
+ signal: combineSignal(signal, model.timeout),
883
+ ...dispatcher ? { dispatcher } : {}
884
+ };
885
+ const response = await fetch(`${model.baseUrl.replace(/\/$/, "")}/audio/speech`, requestInit);
886
+ if (!response.ok)
887
+ return err(HaiAIError.AUDIO_UPSTREAM_ERROR, aiM("ai_audioUpstreamError", { params: { error: await describeHttpError(response) } }));
888
+ const data = new Uint8Array(await response.arrayBuffer());
889
+ return ok(buildSynthesisResult(data, out, response.headers));
890
+ } finally {
891
+ await dispatcher?.close();
892
+ }
886
893
  } catch (error) {
887
894
  logger4.debug("IndexTTS synthesize failed", { error: errorMessage(error) });
888
895
  return toAudioErrorResult(error, signal);
@@ -893,6 +900,16 @@ function createIndexTtsAudioProvider() {
893
900
  getCapabilities: () => INDEX_TTS_CAPABILITIES
894
901
  };
895
902
  }
903
+ async function createNodeDispatcher(timeout) {
904
+ if (typeof process2 === "undefined" || !process2.versions?.node)
905
+ return void 0;
906
+ const { Agent } = await import('undici');
907
+ return new Agent({
908
+ connectTimeout: Math.min(timeout, 3e4),
909
+ headersTimeout: timeout,
910
+ bodyTimeout: timeout
911
+ });
912
+ }
896
913
  function resolveSynthesisOutput2(request) {
897
914
  const format = request.format ?? "wav";
898
915
  return { format, sampleRate: format === "pcm16" ? request.sampleRate ?? 24e3 : void 0, channels: 1 };
@@ -4820,10 +4837,10 @@ function createAnthropicProvider(deps) {
4820
4837
  function resolveAnthropic(requestModel, tempModel) {
4821
4838
  const entry = requestModel ? config.llm.models?.find((m) => m.id === requestModel || m.model === requestModel) : void 0;
4822
4839
  const model = tempModel?.model ?? entry?.model ?? requestModel ?? config.llm.model ?? "claude-3-5-sonnet-latest";
4823
- const apiKey = tempModel?.apiKey ?? entry?.apiKey ?? config.llm.apiKey ?? process.env.HAI_AI_ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_API_KEY;
4840
+ const apiKey = tempModel?.apiKey ?? entry?.apiKey ?? config.llm.apiKey ?? process2.env.HAI_AI_ANTHROPIC_API_KEY ?? process2.env.ANTHROPIC_API_KEY;
4824
4841
  if (!apiKey)
4825
4842
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_configError", { params: { error: "API Key is required for Anthropic" } }));
4826
- const baseUrl = (tempModel?.baseUrl ?? entry?.baseUrl ?? process.env.HAI_AI_ANTHROPIC_BASE_URL ?? process.env.ANTHROPIC_BASE_URL ?? DEFAULT_ANTHROPIC_BASE_URL).replace(/\/+$/, "");
4843
+ const baseUrl = (tempModel?.baseUrl ?? entry?.baseUrl ?? process2.env.HAI_AI_ANTHROPIC_BASE_URL ?? process2.env.ANTHROPIC_BASE_URL ?? DEFAULT_ANTHROPIC_BASE_URL).replace(/\/+$/, "");
4827
4844
  return ok({
4828
4845
  apiKey,
4829
4846
  baseUrl,
@@ -5182,10 +5199,10 @@ function createOpenAIProvider(deps) {
5182
5199
  return ok({ client: getClient(apiKey ?? "", baseUrl ?? "https://api.openai.com/v1", timeout), model, api: resolvedApi });
5183
5200
  }
5184
5201
  function resolveTempClient(tempModel) {
5185
- const apiKey = tempModel.apiKey ?? config.llm.apiKey ?? process.env.HAI_AI_LLM_API_KEY ?? process.env.OPENAI_API_KEY;
5202
+ const apiKey = tempModel.apiKey ?? config.llm.apiKey ?? process2.env.HAI_AI_LLM_API_KEY ?? process2.env.OPENAI_API_KEY;
5186
5203
  if (!apiKey)
5187
5204
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_configError", { params: { error: "API Key is required for temp model" } }));
5188
- const baseUrl = tempModel.baseUrl ?? config.llm.baseUrl ?? process.env.HAI_AI_LLM_BASE_URL ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1";
5205
+ const baseUrl = tempModel.baseUrl ?? config.llm.baseUrl ?? process2.env.HAI_AI_LLM_BASE_URL ?? process2.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1";
5189
5206
  const timeout = tempModel.timeout ?? config.llm.timeout ?? 6e4;
5190
5207
  const api = (tempModel.api ?? config.llm.api) === "responses" ? "responses" : "chat";
5191
5208
  return ok({
@@ -7927,6 +7944,12 @@ async function createAISubsystems(config, deps) {
7927
7944
  }
7928
7945
  var logger30 = core.logger.child({ module: "ai", scope: "store-provider-db" });
7929
7946
  var MAX_BATCH_SQL_PARAMS = 900;
7947
+ function dataTimestamp(data, field, fallback) {
7948
+ if (typeof data !== "object" || data === null)
7949
+ return fallback;
7950
+ const value = data[field];
7951
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
7952
+ }
7930
7953
  var ReldbAIRelStore = class {
7931
7954
  sql;
7932
7955
  table;
@@ -7982,6 +8005,8 @@ var ReldbAIRelStore = class {
7982
8005
  async save(id, data, scope) {
7983
8006
  const now = Date.now();
7984
8007
  const json = JSON.stringify(data);
8008
+ const createdAt = dataTimestamp(data, "createdAt", now);
8009
+ const updatedAt = dataTimestamp(data, "updatedAt", now);
7985
8010
  const colNames = ["id"];
7986
8011
  const values = [id];
7987
8012
  const placeholders = ["?"];
@@ -8006,7 +8031,7 @@ var ReldbAIRelStore = class {
8006
8031
  placeholders.push("?");
8007
8032
  }
8008
8033
  colNames.push("data", "created_at", "updated_at");
8009
- values.push(json, now, now);
8034
+ values.push(json, createdAt, updatedAt);
8010
8035
  placeholders.push("?", "?", "?");
8011
8036
  if (this.dbType === "mysql") {
8012
8037
  const updateParts = ["data = VALUES(data)", "updated_at = VALUES(updated_at)"];
@@ -8140,7 +8165,11 @@ var ReldbAIRelStore = class {
8140
8165
  values.push(scope?.status ?? null);
8141
8166
  if (this.hasRefId)
8142
8167
  values.push(scope?.refId ?? null);
8143
- values.push(JSON.stringify(data), now, now);
8168
+ values.push(
8169
+ JSON.stringify(data),
8170
+ dataTimestamp(data, "createdAt", now),
8171
+ dataTimestamp(data, "updatedAt", now)
8172
+ );
8144
8173
  return values;
8145
8174
  }
8146
8175
  /** 构造跨数据库 upsert 更新片段;列名来自内部白名单,不接受外部输入。 */