@h-ai/ai 1.0.0-beta.6 → 1.0.0-beta.8

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/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { aiM, AIConfigSchema, MemoryConfigSchema, RetrievalConfigSchema, KnowledgeConfigSchema, TokenConfigSchema, SummaryConfigSchema, CompressConfigSchema, AudioConfigSchema, ImageConfigSchema, resolveImageModel, resolveModelEntry, resolveAudioModel, ensureAudioCredential, resolveModelApi } from './chunk-GAC5RLAG.js';
2
- export { A2AConfigSchema, A2ASkillConfigSchema, AIConfigSchema, AUDIO_WS_PATH, ApiTypeSchema, AudioConfigSchema, AudioFormatSchema, AudioModelEntrySchema, AudioOperationSchema, AudioOperationsSchema, AudioProviderSchema, AudioWsClientMessageSchema, AudioWsDoneMessageSchema, AudioWsStartMessageSchema, AudioWsTextMessageSchema, CompressConfigSchema, CompressionStrategySchema, EmbeddingConfigSchema, EntityTypeSchema, FileConfigSchema, ImageConfigSchema, ImageModelEntrySchema, ImageProviderSchema, KnowledgeConfigSchema, LLMConfigSchema, MCPConfigSchema, MCPServerCapabilitiesSchema, MCPServerConfigSchema, MemoryConfigSchema, MemoryTypeSchema, ModelEntrySchema, ModelScenarioSchema, OptionalSecretSchema, RetrievalConfigSchema, RetrievalSourceSchema, SummaryConfigSchema, TokenConfigSchema, ensureAudioCredential, resolveAudioModel, resolveImageModel, resolveModelApi, resolveModelEntry, serializePlayableAudio } from './chunk-GAC5RLAG.js';
3
- import { HaiAIError } from './chunk-HYMP57AC.js';
4
- export { HaiAIError } from './chunk-HYMP57AC.js';
5
- import process from 'process';
1
+ import { aiM, AIConfigSchema, MemoryConfigSchema, ModelEntrySchema, AudioModelEntrySchema, ImageModelEntrySchema, MCPExternalServerSchema, RetrievalSourceSchema, KnowledgeConfigSchema, TokenConfigSchema, SummaryConfigSchema, CompressConfigSchema, ACPConfigSchema, ACPAgentSchema, ACPWorkspaceSchema, AudioConfigSchema, resolveModelEntry, resolveAudioModel, ensureAudioCredential, resolveModelApi, sessionIdentitySchema, ImageConfigSchema, resolveImageModel, ACPPublishSchema, branchNameSchema } from './chunk-HEYVFQC6.js';
2
+ export { A2AConfigSchema, A2ASkillConfigSchema, ACPAgentSchema, ACPConfigSchema, ACPPublishSchema, ACPRepositorySchema, ACPWorkspaceSchema, AIConfigSchema, AUDIO_WS_PATH, ApiTypeSchema, AudioConfigSchema, AudioFormatSchema, AudioModelEntrySchema, AudioOperationSchema, AudioOperationsSchema, AudioProviderSchema, AudioWsClientMessageSchema, AudioWsDoneMessageSchema, AudioWsStartMessageSchema, AudioWsTextMessageSchema, CompressConfigSchema, CompressionStrategySchema, EmbeddingConfigSchema, EntityTypeSchema, FileConfigSchema, ImageConfigSchema, ImageModelEntrySchema, ImageProviderSchema, KnowledgeConfigSchema, LLMConfigSchema, MCPConfigSchema, MCPExternalServerSchema, MCPServerCapabilitiesSchema, MCPServerConfigSchema, MemoryConfigSchema, MemoryTypeSchema, ModelCapabilitiesSchema, ModelEntrySchema, ModelScenarioSchema, OptionalSecretSchema, RetrievalConfigSchema, RetrievalSourceSchema, SummaryConfigSchema, TokenConfigSchema, ensureAudioCredential, resolveAudioModel, resolveImageModel, resolveModelApi, resolveModelEntry, serializePlayableAudio } from './chunk-HEYVFQC6.js';
3
+ import { HaiAIError } from './chunk-M77C3VAP.js';
4
+ export { HaiAIError } from './chunk-M77C3VAP.js';
5
+ import process2 from 'process';
6
6
  import { core, err, ok } from '@h-ai/core';
7
7
  import { datapipe } from '@h-ai/datapipe';
8
8
  import { A2AClient } from '@a2a-js/sdk/client';
@@ -10,14 +10,21 @@ import { JsonRpcTransportHandler, ServerCallContext, A2AError, RequestContext, R
10
10
  import { Buffer } from 'buffer';
11
11
  import { createHash, randomUUID } from 'crypto';
12
12
  import { setTimeout as setTimeout$1 } from 'timers/promises';
13
+ import { z, ZodError } from 'zod';
14
+ import { mkdir, realpath } from 'fs/promises';
15
+ import { isAbsolute, resolve, relative, dirname, basename } from 'path';
16
+ import { spawn } from 'child_process';
17
+ import { Writable, Readable } from 'stream';
18
+ import { methods, ndJsonStream, client, PROTOCOL_VERSION } from '@agentclientprotocol/sdk';
19
+ import { createHttpStream } from '@agentclientprotocol/sdk/experimental/http-client';
20
+ import { createWebSocketStream } from '@agentclientprotocol/sdk/experimental/ws-client';
21
+ import WebSocket2, { WebSocket } from 'ws';
13
22
  import { gunzipSync } from 'zlib';
14
- import WebSocket from 'ws';
15
23
  import { FormData as FormData$1, Agent, fetch as fetch$1 } from 'undici';
16
24
  import OpenAI3, { toFile } from 'openai';
17
25
  import { nanoid } from 'nanoid';
18
26
  import dns from 'dns';
19
27
  import { BlockList, isIP } from 'net';
20
- import { z } from 'zod';
21
28
  import { PromptMessageSchema, TextResourceContentsSchema, BlobResourceContentsSchema } from '@modelcontextprotocol/sdk/types.js';
22
29
  import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv';
23
30
  import { reldb } from '@h-ai/reldb';
@@ -520,7 +527,7 @@ async function executeRemoteAgentCall(remoteUrl, message, options) {
520
527
  safeLabel = validatedUrl.safeLabel;
521
528
  const customHeaders = options?.headers;
522
529
  const timeoutMs = Math.max(1, options?.timeout ?? DEFAULT_A2A_CALL_TIMEOUT_MS);
523
- const client = new A2AClient(requestUrl, {
530
+ const client2 = new A2AClient(requestUrl, {
524
531
  fetchImpl: ((input, init) => {
525
532
  const existingHeaders = init?.headers ? Object.fromEntries(new Headers(init.headers).entries()) : {};
526
533
  const signals = [init?.signal, AbortSignal.timeout(timeoutMs)].filter((signal) => Boolean(signal));
@@ -540,7 +547,7 @@ async function executeRemoteAgentCall(remoteUrl, message, options) {
540
547
  parts: [{ kind: "text", text: message }]
541
548
  }
542
549
  };
543
- const response = await client.sendMessage(params);
550
+ const response = await client2.sendMessage(params);
544
551
  if ("error" in response) {
545
552
  return err(HaiAIError.A2A_REMOTE_CALL_FAILED, aiM("ai_a2aRemoteCallFailed", { params: { url: safeLabel, error: "Remote agent returned an error" } }));
546
553
  }
@@ -702,6 +709,2766 @@ function createA2ALazyProxy(deps) {
702
709
  }
703
710
  };
704
711
  }
712
+ var ACPFailure = class extends Error {
713
+ /**
714
+ * 创建只携带允许公开诊断信息的内部异常。
715
+ * @param kind - 固定错误类别。
716
+ * @param details - 脱敏数据,不得包含凭据或原始 stderr。
717
+ * @returns 内部异常实例。
718
+ * @example
719
+ * ```ts
720
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
721
+ * const failure = new ACPFailure('busy')
722
+ * ```
723
+ */
724
+ constructor(kind, details) {
725
+ super(kind);
726
+ this.kind = kind;
727
+ this.details = details;
728
+ }
729
+ kind;
730
+ details;
731
+ };
732
+ var mappings = {
733
+ invalid: ["ACP_INVALID_REQUEST", "ai_acpInvalid"],
734
+ missing: ["ACP_NOT_FOUND", "ai_acpNotFound"],
735
+ busy: ["ACP_BUSY", "ai_acpBusy"],
736
+ unsupported: ["ACP_UNSUPPORTED", "ai_acpUnsupported"],
737
+ cancelled: ["ACP_CANCELLED", "ai_acpCancelled"],
738
+ timeout: ["ACP_TIMEOUT", "ai_acpTimeout"],
739
+ output: ["ACP_OUTPUT_INVALID", "ai_acpOutput"],
740
+ conflict: ["ACP_CONFLICT", "ai_acpConflict"],
741
+ partial: ["ACP_PARTIAL_FAILURE", "ai_acpPartial"],
742
+ closed: ["ACP_CLOSED", "ai_acpClosed"],
743
+ runtime: ["ACP_RUNTIME_UNAVAILABLE", "ai_acpRuntime"],
744
+ model: ["ACP_MODEL_INCOMPATIBLE", "ai_acpModel"],
745
+ cleanup: ["ACP_CLEANUP_FAILED", "ai_acpCleanup"],
746
+ gitMissing: ["ACP_GIT_NOT_FOUND", "ai_acpGitNotFound"],
747
+ failed: ["ACP_OPERATION_FAILED", "ai_acpOperationFailed"]
748
+ };
749
+ function acpError(error) {
750
+ const failure = error instanceof ACPFailure ? error : new ACPFailure(error instanceof ZodError ? "invalid" : "failed");
751
+ const [code, message] = mappings[failure.kind];
752
+ return err(HaiAIError[code], aiM(message), failure.details);
753
+ }
754
+
755
+ // src/acp/ai-acp-adapters.ts
756
+ function resolveAgentModels(agent, config) {
757
+ return agent.modelIds.map((id) => {
758
+ if (!config.llm?.models?.some((entry2) => entry2.id === id))
759
+ throw new ACPFailure("model", { modelId: id });
760
+ const entry = config.llm.models.find((model) => model.id === id);
761
+ const protocol = entry.protocol ?? config.llm.protocol;
762
+ const llm = protocol === "anthropic" ? {
763
+ ...config.llm,
764
+ apiKey: entry.apiKey ?? config.llm.apiKey ?? (process2.env.HAI_AI_LLM_APIKEY?.trim() || process2.env.ANTHROPIC_API_KEY),
765
+ baseUrl: entry.baseUrl ?? (config.llm.protocol === "anthropic" ? config.llm.baseUrl ?? process2.env.HAI_AI_LLM_BASEURL : void 0) ?? process2.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com"
766
+ } : config.llm;
767
+ if (protocol === "anthropic" && !llm.apiKey)
768
+ throw new ACPFailure("model", { modelId: id });
769
+ const result = resolveModelEntry(llm, "default", id);
770
+ if (!result.success)
771
+ throw new ACPFailure("model", { modelId: id });
772
+ if (result.data.capabilities?.toolCalling === false)
773
+ throw new ACPFailure("model", { modelId: id });
774
+ return { id, config: result.data };
775
+ });
776
+ }
777
+ function resolveEnvironment(references) {
778
+ const env = {};
779
+ for (const [key, source] of Object.entries(references)) {
780
+ const value = process2.env[source];
781
+ if (!value)
782
+ throw new ACPFailure("invalid", { environment: source });
783
+ env[key] = value;
784
+ }
785
+ return env;
786
+ }
787
+ var nativeAdapters = {
788
+ "deepseek-harness": {
789
+ configure({ agent, models, modelId, statePath }) {
790
+ const selected = models.findIndex((item) => item.id === modelId);
791
+ if (selected < 0)
792
+ throw new ACPFailure("model", { modelId });
793
+ const env = { DSH_HOME: statePath };
794
+ const providers = {};
795
+ for (const [index, entry] of models.entries()) {
796
+ const model = entry.config;
797
+ const caps = model.capabilities;
798
+ if (!caps?.contextWindow || !caps.maxOutputTokens || !caps.inputModalities || caps.toolCalling !== true || !caps.reasoning || caps.reasoning.supported && !caps.reasoning.efforts?.length) {
799
+ throw new ACPFailure("model", { modelId: entry.id });
800
+ }
801
+ if (caps.inputModalities.some((item) => !["text", "image"].includes(item)))
802
+ throw new ACPFailure("model", { modelId: entry.id });
803
+ const key = `HAI_ACP_MODEL_${index}`;
804
+ env[key] = model.apiKey;
805
+ providers[`hai-${index}`] = {
806
+ apiKeyEnv: key,
807
+ baseURL: model.baseUrl,
808
+ api: model.protocol === "chat" ? "openai-completions" : model.protocol === "responses" ? "openai-responses" : "anthropic-messages",
809
+ // 输出容量是能力声明;单轮预算沿用 Hai 模型的 maxTokens。
810
+ defaultMaxTokens: Math.min(model.maxTokens, caps.maxOutputTokens),
811
+ defaultContextWindow: caps.contextWindow,
812
+ timeoutMs: model.timeout,
813
+ retryPolicy: { mode: "normal", maxRetries: 0 },
814
+ models: [
815
+ {
816
+ id: model.model,
817
+ contextWindow: caps.contextWindow,
818
+ input: caps.inputModalities,
819
+ reasoningEfforts: caps.reasoning.supported ? Object.fromEntries(
820
+ (caps.reasoning.efforts ?? []).map((effort) => [
821
+ effort,
822
+ effort
823
+ ])
824
+ ) : false
825
+ }
826
+ ]
827
+ };
828
+ }
829
+ const patch = [
830
+ { id: "llm-pi-ai", config: { providers } },
831
+ {
832
+ id: "acp",
833
+ config: {
834
+ provider: `hai-${selected}`,
835
+ model: models[selected].config.model
836
+ }
837
+ }
838
+ ];
839
+ return {
840
+ command: agent.mode === "custom-service" ? "dsh" : agent.command ?? "dsh",
841
+ args: [
842
+ // 自定义命令可能是 node + JS 入口,入口参数必须先于 dsh 的原生选项。
843
+ ...agent.mode === "custom-service" ? [] : agent.args ?? [],
844
+ "--profile",
845
+ "acp",
846
+ "--patch",
847
+ `${statePath}/hai-config.json`
848
+ ],
849
+ env,
850
+ files: { "hai-config.json": JSON.stringify(patch) }
851
+ };
852
+ }
853
+ },
854
+ "generic": {
855
+ configure({ agent, models }) {
856
+ if (agent.mode === "custom-service" || !agent.command || models.length)
857
+ throw new ACPFailure("model");
858
+ return { command: agent.command, args: agent.args ?? [], env: {} };
859
+ }
860
+ },
861
+ "codex": {
862
+ configure({ agent, models, modelId, statePath }) {
863
+ const selected = models.find((item) => item.id === modelId);
864
+ if (!selected || selected.config.protocol !== "responses")
865
+ throw new ACPFailure("model", { modelId });
866
+ const model = selected.config;
867
+ return {
868
+ command: agent.mode === "custom-service" ? "codex-acp" : agent.command ?? "codex-acp",
869
+ args: agent.mode === "custom-service" ? [] : agent.args ?? [],
870
+ env: {
871
+ CODEX_HOME: statePath,
872
+ CODEX_API_KEY: model.apiKey,
873
+ OPENAI_API_KEY: model.apiKey,
874
+ NO_BROWSER: "1",
875
+ DEFAULT_AUTH_REQUEST: JSON.stringify({ methodId: "api-key" }),
876
+ MODEL_PROVIDER: "hai",
877
+ CODEX_CONFIG: JSON.stringify({
878
+ model: model.model,
879
+ model_provider: "hai",
880
+ model_context_window: model.capabilities?.contextWindow,
881
+ model_providers: {
882
+ hai: {
883
+ name: "Hai",
884
+ base_url: model.baseUrl,
885
+ wire_api: "responses",
886
+ env_key: "CODEX_API_KEY"
887
+ }
888
+ }
889
+ })
890
+ }
891
+ };
892
+ }
893
+ }
894
+ };
895
+ function createResourceRegistry(store, schema, configured, isOpen) {
896
+ const config = new Map(
897
+ configured.map((item) => [item.id, structuredClone(item)])
898
+ );
899
+ if (config.size !== configured.length)
900
+ throw new Error("Duplicate configuration resource ID");
901
+ const copy = (record) => {
902
+ const { __deleted: _deleted, ...value } = record;
903
+ return structuredClone(value);
904
+ };
905
+ function ensure() {
906
+ if (!isOpen())
907
+ throw new Error("Resource registry closed");
908
+ }
909
+ async function read(id) {
910
+ ensure();
911
+ const fixed = config.get(id);
912
+ if (fixed)
913
+ return { value: structuredClone(fixed), source: "config" };
914
+ const saved = await store.readVersioned(id);
915
+ return saved && !saved.data.__deleted ? { value: copy(saved.data), source: "database", version: saved.version } : void 0;
916
+ }
917
+ async function entries() {
918
+ ensure();
919
+ const saved = await store.query({});
920
+ return [
921
+ ...[...config.values()].map((value) => ({
922
+ value: structuredClone(value),
923
+ source: "config"
924
+ })),
925
+ ...saved.filter((item) => !item.__deleted && !config.has(item.id)).map((item) => ({ value: copy(item), source: "database" }))
926
+ ];
927
+ }
928
+ async function protect(operation) {
929
+ if (!isOpen())
930
+ return err(HaiAIError.NOT_INITIALIZED, aiM("ai_notInitialized"));
931
+ try {
932
+ return ok(await operation());
933
+ } catch {
934
+ return err(
935
+ HaiAIError.RESOURCE_OPERATION_FAILED,
936
+ aiM("ai_resourceOperationFailed")
937
+ );
938
+ }
939
+ }
940
+ async function write(input, update, expectedVersion) {
941
+ if (!isOpen())
942
+ return err(HaiAIError.NOT_INITIALIZED, aiM("ai_notInitialized"));
943
+ const parsed = schema.safeParse(input);
944
+ if (!parsed.success || !parsed.data.id)
945
+ return err(HaiAIError.RESOURCE_INVALID, aiM("ai_resourceInvalid"));
946
+ const value = parsed.data;
947
+ if (config.has(value.id))
948
+ return err(HaiAIError.RESOURCE_READ_ONLY, aiM("ai_resourceReadOnly"));
949
+ try {
950
+ const previous = await store.readVersioned(value.id);
951
+ const exists = previous && !previous.data.__deleted;
952
+ if (update && !exists)
953
+ return err(HaiAIError.RESOURCE_NOT_FOUND, aiM("ai_resourceNotFound"));
954
+ if (!update && exists || expectedVersion !== void 0 && expectedVersion !== previous?.version) {
955
+ return err(HaiAIError.RESOURCE_CONFLICT, aiM("ai_resourceConflict"));
956
+ }
957
+ const version = await store.compareAndSwap(value.id, {
958
+ expectedVersion: previous?.version,
959
+ data: value
960
+ });
961
+ if (!version)
962
+ return err(HaiAIError.RESOURCE_CONFLICT, aiM("ai_resourceConflict"));
963
+ return ok({
964
+ value: structuredClone(value),
965
+ source: "database",
966
+ version
967
+ });
968
+ } catch {
969
+ return err(
970
+ HaiAIError.RESOURCE_OPERATION_FAILED,
971
+ aiM("ai_resourceOperationFailed")
972
+ );
973
+ }
974
+ }
975
+ const operations = {
976
+ list: () => protect(entries),
977
+ get: async (id) => {
978
+ const result = await protect(() => read(id));
979
+ if (!result.success)
980
+ return result;
981
+ return result.data ? ok(result.data) : err(HaiAIError.RESOURCE_NOT_FOUND, aiM("ai_resourceNotFound"));
982
+ },
983
+ register: (input) => write(input, false),
984
+ update: (input, version) => write(input, true, version),
985
+ remove: async (id, expectedVersion) => {
986
+ if (!isOpen())
987
+ return err(HaiAIError.NOT_INITIALIZED, aiM("ai_notInitialized"));
988
+ if (config.has(id))
989
+ return err(HaiAIError.RESOURCE_READ_ONLY, aiM("ai_resourceReadOnly"));
990
+ try {
991
+ const previous = await store.readVersioned(id);
992
+ if (!previous || previous.data.__deleted)
993
+ return err(HaiAIError.RESOURCE_NOT_FOUND, aiM("ai_resourceNotFound"));
994
+ if (expectedVersion !== void 0 && expectedVersion !== previous.version) {
995
+ return err(HaiAIError.RESOURCE_CONFLICT, aiM("ai_resourceConflict"));
996
+ }
997
+ if (!await store.compareAndSwap(id, {
998
+ expectedVersion: previous.version,
999
+ data: { ...previous.data, __deleted: true }
1000
+ })) {
1001
+ return err(HaiAIError.RESOURCE_CONFLICT, aiM("ai_resourceConflict"));
1002
+ }
1003
+ return ok(void 0);
1004
+ } catch {
1005
+ return err(
1006
+ HaiAIError.RESOURCE_OPERATION_FAILED,
1007
+ aiM("ai_resourceOperationFailed")
1008
+ );
1009
+ }
1010
+ }
1011
+ };
1012
+ return {
1013
+ operations,
1014
+ read,
1015
+ entries,
1016
+ values: async () => (await entries()).map((entry) => entry.value)
1017
+ };
1018
+ }
1019
+ var nativeJob = String.raw`
1020
+ using System;
1021
+ using System.ComponentModel;
1022
+ using System.Runtime.InteropServices;
1023
+ using System.Text;
1024
+ using System.Threading;
1025
+
1026
+ public static class HaiACPCommandJob {
1027
+ [StructLayout(LayoutKind.Sequential)] struct BasicLimits {
1028
+ public long ProcessTime, JobTime;
1029
+ public uint Flags;
1030
+ public UIntPtr MinimumWorkingSet, MaximumWorkingSet;
1031
+ public uint ActiveProcessLimit;
1032
+ public UIntPtr Affinity;
1033
+ public uint PriorityClass, SchedulingClass;
1034
+ }
1035
+ [StructLayout(LayoutKind.Sequential)] struct IoCounters {
1036
+ public ulong ReadOperations, WriteOperations, OtherOperations, ReadBytes, WriteBytes, OtherBytes;
1037
+ }
1038
+ [StructLayout(LayoutKind.Sequential)] struct ExtendedLimits {
1039
+ public BasicLimits Basic;
1040
+ public IoCounters Io;
1041
+ public UIntPtr ProcessMemory, JobMemory, PeakProcessMemory, PeakJobMemory;
1042
+ }
1043
+ [StructLayout(LayoutKind.Sequential)] struct Accounting {
1044
+ public long UserTime, KernelTime, PeriodUserTime, PeriodKernelTime;
1045
+ public uint Faults, Total, Active, Terminated;
1046
+ }
1047
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct Startup {
1048
+ public uint Size;
1049
+ public string Reserved, Desktop, Title;
1050
+ public uint X, Y, Width, Height, XChars, YChars, Fill, Flags;
1051
+ public ushort Show, ReservedSize;
1052
+ public IntPtr ReservedData, Input, Output, Error;
1053
+ }
1054
+ [StructLayout(LayoutKind.Sequential)] struct ProcessInfo {
1055
+ public IntPtr Process, Thread;
1056
+ public uint ProcessId, ThreadId;
1057
+ }
1058
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern IntPtr CreateJobObject(IntPtr attributes, string name);
1059
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern IntPtr OpenJobObject(uint access, bool inherit, string name);
1060
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetInformationJobObject(IntPtr job, int kind, ref ExtendedLimits information, uint size);
1061
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool QueryInformationJobObject(IntPtr job, int kind, out Accounting information, uint size, IntPtr length);
1062
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
1063
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern bool CreateProcess(string application, StringBuilder command, IntPtr processAttributes, IntPtr threadAttributes, bool inherit, uint flags, IntPtr environment, string directory, ref Startup startup, out ProcessInfo information);
1064
+ [DllImport("kernel32.dll", SetLastError = true)] static extern uint ResumeThread(IntPtr thread);
1065
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool TerminateProcess(IntPtr process, uint code);
1066
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool TerminateJobObject(IntPtr job, uint code);
1067
+ [DllImport("kernel32.dll", SetLastError = true)] static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
1068
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool GetExitCodeProcess(IntPtr process, out uint code);
1069
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool CloseHandle(IntPtr handle);
1070
+ [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle(int kind);
1071
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetHandleInformation(IntPtr handle, uint mask, uint flags);
1072
+
1073
+ static void Check(bool success) { if (!success) throw new Win32Exception(Marshal.GetLastWin32Error()); }
1074
+ static uint Active(IntPtr job) {
1075
+ Accounting information;
1076
+ Check(QueryInformationJobObject(job, 1, out information, (uint)Marshal.SizeOf(typeof(Accounting)), IntPtr.Zero));
1077
+ return information.Active;
1078
+ }
1079
+ // Windows argv 编码:引号前与结尾的反斜杠加倍,其余字符原样传递,不经过命令 shell。
1080
+ static string Quote(string value) {
1081
+ StringBuilder result = new StringBuilder("\"");
1082
+ int slashes = 0;
1083
+ foreach (char current in value) {
1084
+ if (current == '\\') { slashes++; continue; }
1085
+ if (current == '"') result.Append('\\', slashes * 2 + 1);
1086
+ else result.Append('\\', slashes);
1087
+ result.Append(current);
1088
+ slashes = 0;
1089
+ }
1090
+ result.Append('\\', slashes * 2).Append('"');
1091
+ return result.ToString();
1092
+ }
1093
+ public static int Run(string name, string command, string[] arguments) {
1094
+ IntPtr job = CreateJobObject(IntPtr.Zero, name);
1095
+ if (job == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error());
1096
+ ProcessInfo process = new ProcessInfo();
1097
+ bool assigned = false;
1098
+ try {
1099
+ ExtendedLimits limits = new ExtendedLimits();
1100
+ limits.Basic.Flags = 0x2000; // JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;不允许 breakaway。
1101
+ Check(SetInformationJobObject(job, 9, ref limits, (uint)Marshal.SizeOf(typeof(ExtendedLimits))));
1102
+ StringBuilder line = new StringBuilder(Quote(command));
1103
+ foreach (string argument in arguments) line.Append(' ').Append(Quote(argument));
1104
+ Startup startup = new Startup();
1105
+ startup.Size = (uint)Marshal.SizeOf(typeof(Startup));
1106
+ startup.Flags = 0x100;
1107
+ startup.Input = GetStdHandle(-10); startup.Output = GetStdHandle(-11); startup.Error = GetStdHandle(-12);
1108
+ Check(SetHandleInformation(startup.Input, 1, 1));
1109
+ Check(SetHandleInformation(startup.Output, 1, 1));
1110
+ Check(SetHandleInformation(startup.Error, 1, 1));
1111
+ Check(CreateProcess(null, line, IntPtr.Zero, IntPtr.Zero, true, 0x08000004, IntPtr.Zero, null, ref startup, out process));
1112
+ Check(AssignProcessToJobObject(job, process.Process));
1113
+ assigned = true;
1114
+ if (ResumeThread(process.Thread) == 0xFFFFFFFF) throw new Win32Exception(Marshal.GetLastWin32Error());
1115
+ if (WaitForSingleObject(process.Process, 0xFFFFFFFF) != 0) throw new Win32Exception(Marshal.GetLastWin32Error());
1116
+ uint code;
1117
+ Check(GetExitCodeProcess(process.Process, out code));
1118
+ while (Active(job) != 0) Thread.Sleep(20);
1119
+ return unchecked((int)code);
1120
+ }
1121
+ finally {
1122
+ if (process.Process != IntPtr.Zero) {
1123
+ // 分配失败时进程仍挂起,直接终止,不允许裸进程恢复;Job 在其他异常时同样清空。
1124
+ if (!assigned) TerminateProcess(process.Process, 125);
1125
+ else TerminateJobObject(job, 125);
1126
+ WaitForSingleObject(process.Process, 5000);
1127
+ }
1128
+ if (process.Thread != IntPtr.Zero) CloseHandle(process.Thread);
1129
+ if (process.Process != IntPtr.Zero) CloseHandle(process.Process);
1130
+ CloseHandle(job);
1131
+ }
1132
+ }
1133
+ public static bool Empty(string name) {
1134
+ IntPtr job = OpenJobObject(4, false, name); // JOB_OBJECT_QUERY
1135
+ if (job == IntPtr.Zero) {
1136
+ if (Marshal.GetLastWin32Error() == 2) return true;
1137
+ throw new Win32Exception(Marshal.GetLastWin32Error());
1138
+ }
1139
+ try { return Active(job) == 0; }
1140
+ finally { CloseHandle(job); }
1141
+ }
1142
+ }
1143
+ `;
1144
+ var compile = `Import-Module ([IO.Path]::Combine($PSHOME, 'Modules', 'Microsoft.PowerShell.Utility', 'Microsoft.PowerShell.Utility.psd1'))
1145
+ Add-Type -TypeDefinition @'
1146
+ ${nativeJob}
1147
+ '@`;
1148
+ function powershell(script) {
1149
+ return ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")];
1150
+ }
1151
+ function windowsACPCommand(command, args) {
1152
+ const name = `Local\\hai-ai-acp-${randomUUID()}`;
1153
+ const key = `__HAI_AI_ACP_JOB_${randomUUID().replaceAll("-", "")}`;
1154
+ const script = `$ErrorActionPreference='Stop'; $ProgressPreference='SilentlyContinue'; $InformationPreference='SilentlyContinue'
1155
+ try {
1156
+ ${compile}
1157
+ $inputConfig = [Environment]::GetEnvironmentVariable('${key}') | ConvertFrom-Json
1158
+ [Environment]::SetEnvironmentVariable('${key}', $null)
1159
+ exit ([HaiACPCommandJob]::Run([string]$inputConfig.name, [string]$inputConfig.command, [string[]]$inputConfig.args))
1160
+ } catch { [Console]::Error.WriteLine('Windows ACP job failed'); exit 125 }`;
1161
+ return { name, args: powershell(script), env: { [key]: JSON.stringify({ name, command, args }) } };
1162
+ }
1163
+ function windowsACPJobProbe(name) {
1164
+ return powershell(`$ErrorActionPreference='Stop'; $ProgressPreference='SilentlyContinue'; $InformationPreference='SilentlyContinue'
1165
+ try {
1166
+ ${compile}
1167
+ if ([HaiACPCommandJob]::Empty('${name}')) { exit 0 } else { exit 1 }
1168
+ } catch {
1169
+ $cause = $_.Exception.GetBaseException()
1170
+ [Console]::Error.WriteLine(('Windows ACP job probe failed ({0}; win32={1})' -f $cause.GetType().Name, $cause.NativeErrorCode))
1171
+ exit 2
1172
+ }`);
1173
+ }
1174
+ async function jobProbe(name, timeout) {
1175
+ return new Promise((resolve3) => {
1176
+ const probe = spawn("powershell.exe", windowsACPJobProbe(name), { shell: false, windowsHide: true, stdio: "ignore", env: baseEnvironment() });
1177
+ let settled = false;
1178
+ let timer;
1179
+ const finish = (code) => {
1180
+ if (settled)
1181
+ return;
1182
+ settled = true;
1183
+ clearTimeout(timer);
1184
+ resolve3(code);
1185
+ };
1186
+ timer = setTimeout(() => {
1187
+ void stopProcess(probe).catch(() => {
1188
+ }).finally(() => finish(2));
1189
+ probe.kill("SIGKILL");
1190
+ finish(2);
1191
+ }, Math.max(1, timeout));
1192
+ probe.once("error", () => finish(2));
1193
+ probe.once("close", (code) => finish(code ?? 2));
1194
+ });
1195
+ }
1196
+ async function stopWindowsJob(child, name, closeObserved) {
1197
+ const deadline = performance.now() + 15e3;
1198
+ let timer;
1199
+ try {
1200
+ await Promise.race([
1201
+ stopProcess(child, { closeObserved }),
1202
+ new Promise((_, reject) => {
1203
+ timer = setTimeout(() => {
1204
+ child.kill("SIGKILL");
1205
+ reject(new ACPFailure("cleanup"));
1206
+ }, Math.max(1, deadline - performance.now()));
1207
+ })
1208
+ ]);
1209
+ while (performance.now() < deadline) {
1210
+ const state = await jobProbe(name, deadline - performance.now());
1211
+ if (state === 0)
1212
+ return;
1213
+ if (state !== 1)
1214
+ throw new ACPFailure("cleanup");
1215
+ await new Promise((resolve3) => setTimeout(resolve3, 20));
1216
+ }
1217
+ throw new ACPFailure("cleanup");
1218
+ } finally {
1219
+ clearTimeout(timer);
1220
+ }
1221
+ }
1222
+ async function stopProcess(child, options = {}) {
1223
+ if (process2.platform === "win32" && options.windowsJob)
1224
+ return stopWindowsJob(child, options.windowsJob, options.closeObserved);
1225
+ const exited = child.exitCode !== null || child.signalCode !== null;
1226
+ if (!child.pid || exited && !options.ownedTree)
1227
+ return;
1228
+ if (process2.platform === "win32" && exited && options.ownedTree)
1229
+ throw new ACPFailure("cleanup");
1230
+ const closed = options.closeObserved ? Promise.resolve() : new Promise((resolve3) => child.once("close", () => resolve3()));
1231
+ if (process2.platform === "win32") {
1232
+ await new Promise((resolve3, reject) => {
1233
+ const killer = spawn(
1234
+ "taskkill",
1235
+ ["/PID", String(child.pid), "/T", "/F"],
1236
+ { windowsHide: true, stdio: "ignore" }
1237
+ );
1238
+ killer.once("error", reject);
1239
+ killer.once("exit", (code) => code === 0 || !options.ownedTree && child.exitCode !== null ? resolve3() : reject(new ACPFailure("cleanup")));
1240
+ });
1241
+ } else {
1242
+ try {
1243
+ process2.kill(-child.pid, "SIGKILL");
1244
+ } catch (error) {
1245
+ if (error.code !== "ESRCH")
1246
+ throw new ACPFailure("cleanup");
1247
+ }
1248
+ }
1249
+ await closed;
1250
+ }
1251
+ function baseEnvironment() {
1252
+ const output = {};
1253
+ for (const [key, value] of Object.entries(process2.env)) {
1254
+ if (value !== void 0 && /^(?:path|pathext|systemroot|windir|comspec|temp|tmp|lang|lc_all|ssl_cert_file|node_extra_ca_certs)$/i.test(
1255
+ key
1256
+ )) {
1257
+ output[key] = value;
1258
+ }
1259
+ }
1260
+ return output;
1261
+ }
1262
+ function engineEnvironment() {
1263
+ const output = baseEnvironment();
1264
+ for (const [key, value] of Object.entries(process2.env)) {
1265
+ if (value !== void 0 && /^(?:HOME|USERPROFILE|APPDATA|LOCALAPPDATA|XDG_CONFIG_HOME|XDG_RUNTIME_DIR|DOCKER_HOST|DOCKER_CONTEXT|DOCKER_CONFIG|CONTAINER_HOST|CONTAINER_CONNECTION|SSH_AUTH_SOCK)$/i.test(
1266
+ key
1267
+ )) {
1268
+ output[key] = value;
1269
+ }
1270
+ }
1271
+ return output;
1272
+ }
1273
+ async function execute(command, args, cwd, env, timeout = 12e4) {
1274
+ return await new Promise((resolve3, reject) => {
1275
+ const child = spawn(command, args, {
1276
+ cwd,
1277
+ env: {
1278
+ ...command === "podman" || command === "docker" ? engineEnvironment() : baseEnvironment(),
1279
+ ...env
1280
+ },
1281
+ shell: false,
1282
+ detached: process2.platform !== "win32",
1283
+ windowsHide: true,
1284
+ stdio: ["ignore", "pipe", "pipe"]
1285
+ });
1286
+ let output = "";
1287
+ let failure;
1288
+ const stop = (error) => {
1289
+ failure ??= error;
1290
+ void stopProcess(child).catch((error2) => {
1291
+ failure = error2;
1292
+ });
1293
+ };
1294
+ const timer = setTimeout(() => stop(new ACPFailure("timeout")), timeout);
1295
+ child.stdout.on("data", (chunk) => {
1296
+ output += chunk.toString("utf8");
1297
+ if (Buffer.byteLength(output) > 8 * 1024 * 1024)
1298
+ stop(new ACPFailure("output"));
1299
+ });
1300
+ child.stderr.resume();
1301
+ child.once("error", (error) => {
1302
+ failure = new ACPFailure(
1303
+ command === "git" && error.code === "ENOENT" ? "gitMissing" : "runtime"
1304
+ );
1305
+ });
1306
+ child.once("close", (code) => {
1307
+ clearTimeout(timer);
1308
+ if (failure)
1309
+ reject(failure);
1310
+ else if (code !== 0)
1311
+ reject(new ACPFailure("failed", { exitCode: code }));
1312
+ else resolve3(output.trim());
1313
+ });
1314
+ });
1315
+ }
1316
+
1317
+ // src/acp/ai-acp-client.ts
1318
+ async function connectACP(transport, options = {}, inputLimits) {
1319
+ const limits = ACPConfigSchema.parse(inputLimits ?? {});
1320
+ const handlers = options.handlers ?? {};
1321
+ const capabilities = options.capabilities ?? {};
1322
+ const required2 = [];
1323
+ if (capabilities.fs?.readTextFile)
1324
+ required2.push(methods.client.fs.readTextFile);
1325
+ if (capabilities.fs?.writeTextFile)
1326
+ required2.push(methods.client.fs.writeTextFile);
1327
+ if (capabilities.terminal)
1328
+ required2.push(...Object.values(methods.client.terminal));
1329
+ if (capabilities.elicitation)
1330
+ required2.push(methods.client.elicitation.create);
1331
+ if (required2.some((name) => !Object.hasOwn(handlers, name))) {
1332
+ throw new ACPFailure("invalid");
1333
+ }
1334
+ if (options.signal?.aborted)
1335
+ throw new ACPFailure("cancelled");
1336
+ let stop = async () => {
1337
+ };
1338
+ let stream;
1339
+ if (transport.type === "stdio") {
1340
+ const job = process2.platform === "win32" ? windowsACPCommand(transport.command, transport.args ?? []) : void 0;
1341
+ const child = spawn(job ? "powershell.exe" : transport.command, job?.args ?? transport.args ?? [], {
1342
+ cwd: transport.cwd,
1343
+ env: { ...baseEnvironment(), ...transport.env, ...job?.env },
1344
+ detached: process2.platform !== "win32",
1345
+ shell: false,
1346
+ windowsHide: true,
1347
+ stdio: ["pipe", "pipe", "pipe"]
1348
+ });
1349
+ let childClosed = false;
1350
+ child.once("close", () => {
1351
+ childClosed = true;
1352
+ });
1353
+ child.stderr.resume();
1354
+ await new Promise((resolve3, reject) => {
1355
+ child.once("spawn", resolve3);
1356
+ child.once("error", () => reject(new ACPFailure("runtime")));
1357
+ });
1358
+ stop = () => stopProcess(child, { ownedTree: true, closeObserved: childClosed, windowsJob: job?.name });
1359
+ stream = ndJsonStream(
1360
+ Writable.toWeb(child.stdin),
1361
+ Readable.toWeb(child.stdout),
1362
+ { maxMessageBytes: limits.maxContentBytes }
1363
+ );
1364
+ } else if (transport.type === "http") {
1365
+ stream = createHttpStream(transport.url, {
1366
+ headers: transport.headers,
1367
+ maxMessageBytes: limits.maxContentBytes
1368
+ });
1369
+ } else {
1370
+ class BoundedWebSocket extends WebSocket {
1371
+ constructor(url, protocols, options2) {
1372
+ super(url, protocols, {
1373
+ ...options2,
1374
+ maxPayload: limits.maxContentBytes
1375
+ });
1376
+ }
1377
+ }
1378
+ stream = createWebSocketStream(transport.url, {
1379
+ headers: transport.headers,
1380
+ WebSocket: BoundedWebSocket
1381
+ });
1382
+ }
1383
+ const requests = /* @__PURE__ */ new Set();
1384
+ const originalWriter = stream.writable.getWriter();
1385
+ const guardedStream = {
1386
+ writable: new WritableStream({
1387
+ async write(message) {
1388
+ if ("method" in message && "id" in message && message.id !== void 0 && message.id !== null) {
1389
+ requests.add(message.id);
1390
+ }
1391
+ await originalWriter.write(message);
1392
+ },
1393
+ close: () => originalWriter.close(),
1394
+ abort: (reason) => originalWriter.abort(reason)
1395
+ }),
1396
+ readable: stream.readable.pipeThrough(
1397
+ new TransformStream({
1398
+ transform(message, controller) {
1399
+ if (!("method" in message) && "id" in message && message.id !== void 0 && message.id !== null) {
1400
+ requests.delete(message.id);
1401
+ }
1402
+ controller.enqueue(message);
1403
+ }
1404
+ })
1405
+ )
1406
+ };
1407
+ let sessionId;
1408
+ let sessionOpening = false;
1409
+ let closed = false;
1410
+ let failure;
1411
+ let closePromise;
1412
+ const listeners = /* @__PURE__ */ new Set();
1413
+ const app = client({ name: "hai-ai" });
1414
+ app.onNotification(methods.client.session.update, ({ params }) => {
1415
+ if (sessionId && params.sessionId !== sessionId)
1416
+ return;
1417
+ try {
1418
+ options.onEvent?.(params);
1419
+ for (const listener of listeners) listener(params);
1420
+ } catch (error) {
1421
+ failure = error;
1422
+ void close().catch(() => {
1423
+ });
1424
+ }
1425
+ });
1426
+ function owned(id) {
1427
+ if (!sessionId || id !== sessionId)
1428
+ throw new ACPFailure("missing");
1429
+ }
1430
+ app.onRequest(methods.client.session.requestPermission, async (context) => {
1431
+ owned(context.params.sessionId);
1432
+ const handler = handlers["session/request_permission"];
1433
+ if (!handler)
1434
+ return { outcome: { outcome: "cancelled" } };
1435
+ return handler(context);
1436
+ });
1437
+ app.onRequest(methods.client.fs.readTextFile, async (context) => {
1438
+ owned(context.params.sessionId);
1439
+ const handler = handlers["fs/read_text_file"];
1440
+ if (!handler)
1441
+ throw new ACPFailure("unsupported");
1442
+ return handler(context);
1443
+ });
1444
+ app.onRequest(methods.client.fs.writeTextFile, async (context) => {
1445
+ owned(context.params.sessionId);
1446
+ const handler = handlers["fs/write_text_file"];
1447
+ if (!handler)
1448
+ throw new ACPFailure("unsupported");
1449
+ return handler(context);
1450
+ });
1451
+ app.onRequest(methods.client.terminal.create, async (context) => {
1452
+ owned(context.params.sessionId);
1453
+ const handler = handlers["terminal/create"];
1454
+ if (!handler)
1455
+ throw new ACPFailure("unsupported");
1456
+ return handler(context);
1457
+ });
1458
+ app.onRequest(methods.client.terminal.output, async (context) => {
1459
+ owned(context.params.sessionId);
1460
+ const handler = handlers["terminal/output"];
1461
+ if (!handler)
1462
+ throw new ACPFailure("unsupported");
1463
+ return handler(context);
1464
+ });
1465
+ app.onRequest(methods.client.terminal.release, async (context) => {
1466
+ owned(context.params.sessionId);
1467
+ const handler = handlers["terminal/release"];
1468
+ if (!handler)
1469
+ throw new ACPFailure("unsupported");
1470
+ return handler(context);
1471
+ });
1472
+ app.onRequest(methods.client.terminal.waitForExit, async (context) => {
1473
+ owned(context.params.sessionId);
1474
+ const handler = handlers["terminal/wait_for_exit"];
1475
+ if (!handler)
1476
+ throw new ACPFailure("unsupported");
1477
+ return handler(context);
1478
+ });
1479
+ app.onRequest(methods.client.terminal.kill, async (context) => {
1480
+ owned(context.params.sessionId);
1481
+ const handler = handlers["terminal/kill"];
1482
+ if (!handler)
1483
+ throw new ACPFailure("unsupported");
1484
+ return handler(context);
1485
+ });
1486
+ app.onRequest(methods.client.elicitation.create, async (context) => {
1487
+ if ("sessionId" in context.params && typeof context.params.sessionId === "string") {
1488
+ owned(context.params.sessionId);
1489
+ } else if (!("requestId" in context.params) || typeof context.params.requestId !== "string" && typeof context.params.requestId !== "number" || !requests.has(context.params.requestId)) {
1490
+ throw new ACPFailure("missing");
1491
+ }
1492
+ const handler = handlers["elicitation/create"];
1493
+ if (!handler)
1494
+ throw new ACPFailure("unsupported");
1495
+ return handler(context);
1496
+ });
1497
+ const connection = app.connect(guardedStream);
1498
+ function close() {
1499
+ closed = true;
1500
+ if (!closePromise) {
1501
+ const task = (async () => {
1502
+ try {
1503
+ await stop();
1504
+ } finally {
1505
+ connection.close();
1506
+ }
1507
+ })();
1508
+ closePromise = task;
1509
+ void task.catch(() => {
1510
+ if (closePromise === task)
1511
+ closePromise = void 0;
1512
+ });
1513
+ }
1514
+ return closePromise;
1515
+ }
1516
+ async function request(operation, timeout = limits.requestTimeout, signal, prompt = false, isCancelledResponse) {
1517
+ if (closed)
1518
+ throw new ACPFailure("closed");
1519
+ if (signal?.aborted)
1520
+ throw new ACPFailure("cancelled");
1521
+ let timer;
1522
+ let abort = () => {
1523
+ };
1524
+ let reason;
1525
+ const deadline = new Promise((_, reject) => {
1526
+ const fail = (kind) => {
1527
+ if (reason)
1528
+ return;
1529
+ reason = new ACPFailure(kind);
1530
+ if (prompt && sessionId) {
1531
+ void connection.agent.notify(methods.agent.session.cancel, { sessionId }).catch(() => {
1532
+ });
1533
+ }
1534
+ clearTimeout(timer);
1535
+ timer = setTimeout(
1536
+ () => {
1537
+ void close().then(() => reject(reason), reject);
1538
+ },
1539
+ prompt ? limits.cancelTimeout : 0
1540
+ );
1541
+ };
1542
+ timer = setTimeout(fail, timeout, "timeout");
1543
+ abort = () => fail("cancelled");
1544
+ signal?.addEventListener("abort", abort, { once: true });
1545
+ });
1546
+ try {
1547
+ const output = await Promise.race([operation(), deadline]);
1548
+ if (failure)
1549
+ throw failure;
1550
+ if (reason) {
1551
+ if (!(reason.kind === "cancelled" && prompt && isCancelledResponse?.(output)))
1552
+ await close();
1553
+ throw reason;
1554
+ }
1555
+ return output;
1556
+ } catch (error) {
1557
+ if (failure)
1558
+ throw failure;
1559
+ if (reason)
1560
+ throw reason;
1561
+ throw error;
1562
+ } finally {
1563
+ clearTimeout(timer);
1564
+ signal?.removeEventListener("abort", abort);
1565
+ }
1566
+ }
1567
+ try {
1568
+ let checkMcp2 = function(servers) {
1569
+ for (const server of servers) {
1570
+ if ("type" in server && server.type === "http" && !agentCapabilities.mcpCapabilities?.http) {
1571
+ throw new ACPFailure("unsupported");
1572
+ }
1573
+ if ("type" in server && server.type === "sse" && !agentCapabilities.mcpCapabilities?.sse) {
1574
+ throw new ACPFailure("unsupported");
1575
+ }
1576
+ }
1577
+ };
1578
+ var checkMcp = checkMcp2;
1579
+ const initialized = await request(
1580
+ () => connection.agent.request(methods.agent.initialize, {
1581
+ protocolVersion: PROTOCOL_VERSION,
1582
+ clientCapabilities: capabilities
1583
+ }),
1584
+ limits.requestTimeout,
1585
+ options.signal
1586
+ );
1587
+ if (initialized.protocolVersion !== PROTOCOL_VERSION)
1588
+ throw new ACPFailure("unsupported");
1589
+ const agentCapabilities = initialized.agentCapabilities ?? {};
1590
+ let activePrompt;
1591
+ async function prompt(content, signal) {
1592
+ if (activePrompt)
1593
+ throw new ACPFailure("busy");
1594
+ if (!sessionId)
1595
+ throw new ACPFailure("missing");
1596
+ if (Buffer.byteLength(JSON.stringify(content)) > limits.maxContentBytes)
1597
+ throw new ACPFailure("output");
1598
+ for (const item of content) {
1599
+ if (item.type === "image" && !agentCapabilities.promptCapabilities?.image || item.type === "audio" && !agentCapabilities.promptCapabilities?.audio || item.type === "resource" && !agentCapabilities.promptCapabilities?.embeddedContext) {
1600
+ throw new ACPFailure("unsupported");
1601
+ }
1602
+ }
1603
+ const events = [];
1604
+ let bytes = 0;
1605
+ let text = "";
1606
+ let messageId;
1607
+ const listener = (event) => {
1608
+ bytes += Buffer.byteLength(JSON.stringify(event));
1609
+ if (events.length >= limits.maxPendingEvents || bytes > limits.maxContentBytes) {
1610
+ throw new ACPFailure("output");
1611
+ }
1612
+ events.push(event);
1613
+ if (event.update.sessionUpdate === "agent_message_chunk" && event.update.content.type === "text") {
1614
+ if (event.update.messageId && event.update.messageId !== messageId)
1615
+ text = "";
1616
+ messageId = event.update.messageId;
1617
+ text += event.update.content.text;
1618
+ }
1619
+ };
1620
+ listeners.add(listener);
1621
+ activePrompt = new AbortController();
1622
+ try {
1623
+ const response = await request(
1624
+ () => connection.agent.request(methods.agent.session.prompt, {
1625
+ sessionId,
1626
+ prompt: content
1627
+ }),
1628
+ limits.promptTimeout,
1629
+ signal ? AbortSignal.any([activePrompt.signal, signal]) : activePrompt.signal,
1630
+ true,
1631
+ (output) => output.stopReason === "cancelled"
1632
+ );
1633
+ if (response.stopReason === "cancelled")
1634
+ throw new ACPFailure("cancelled");
1635
+ return { ...response, text, events };
1636
+ } finally {
1637
+ listeners.delete(listener);
1638
+ activePrompt = void 0;
1639
+ }
1640
+ }
1641
+ return {
1642
+ initialized,
1643
+ /**
1644
+ * 关闭传输并终止拥有的进程树,可重复调用。
1645
+ * @returns 清理完成的 Promise;清理失败抛错。
1646
+ * @example
1647
+ * ```ts
1648
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
1649
+ * await connection.close()
1650
+ * ```
1651
+ */
1652
+ close,
1653
+ /**
1654
+ * 执行单轮原生请求并收集有限事件。
1655
+ * @param content - 原生内容块。
1656
+ * @param signal - 本轮取消信号。
1657
+ * @returns 最终文本、停止原因及事件;业务边界负责转换异常。
1658
+ * @example
1659
+ * ```ts
1660
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
1661
+ * const response = await connection.prompt([{ type: 'text', text: '检查工程' }])
1662
+ * ```
1663
+ */
1664
+ prompt,
1665
+ /**
1666
+ * 订阅会话事件;返回取消订阅函数。
1667
+ * @param listener - 消费原生事件的回调;抛错会关闭连接。
1668
+ * @returns 取消订阅函数。
1669
+ * @example
1670
+ * ```ts
1671
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1672
+ * const unsubscribe = connection.subscribe(event => consume(event)); unsubscribe()
1673
+ * ```
1674
+ */
1675
+ subscribe(listener) {
1676
+ listeners.add(listener);
1677
+ return () => listeners.delete(listener);
1678
+ },
1679
+ /**
1680
+ * 发送原生取消通知并触发正在执行轮次的取消期限。
1681
+ * @returns 通知发送后的 Promise;本轮终止由 prompt 的取消期限保证。
1682
+ * @example
1683
+ * ```ts
1684
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1685
+ * await connection.cancel()
1686
+ * ```
1687
+ */
1688
+ cancel: async () => {
1689
+ activePrompt?.abort();
1690
+ if (sessionId && !closed) {
1691
+ await connection.agent.notify(methods.agent.session.cancel, {
1692
+ sessionId
1693
+ });
1694
+ }
1695
+ },
1696
+ /**
1697
+ * 选择握手 authMethods 中的认证方法,凭据由原生进程或远程服务提供。
1698
+ * @param methodId - 对端握手声明的认证方法。
1699
+ * @returns 原生认证响应。
1700
+ * @example
1701
+ * ```ts
1702
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1703
+ * await connection.authenticate('api-key')
1704
+ * ```
1705
+ */
1706
+ authenticate: (methodId) => request(
1707
+ () => connection.agent.request(methods.agent.authenticate, { methodId })
1708
+ ),
1709
+ /**
1710
+ * 创建此连接唯一的原生会话;MCP 类型必须由智能体声明支持。
1711
+ * @param cwd - 对端可见的绝对工作目录。
1712
+ * @param mcpServers - 原生 MCP 定义,默认空列表。
1713
+ * @returns 包含原生 sessionId 的新建响应。
1714
+ * @example
1715
+ * ```ts
1716
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1717
+ * await connection.createSession('/srv/project', [])
1718
+ * ```
1719
+ */
1720
+ async createSession(cwd, mcpServers = []) {
1721
+ if (sessionId || sessionOpening)
1722
+ throw new ACPFailure("busy");
1723
+ sessionOpening = true;
1724
+ try {
1725
+ checkMcp2(mcpServers);
1726
+ const response = await request(
1727
+ () => connection.agent.request(methods.agent.session.new, {
1728
+ cwd,
1729
+ mcpServers
1730
+ })
1731
+ );
1732
+ sessionId = response.sessionId;
1733
+ return response;
1734
+ } finally {
1735
+ sessionOpening = false;
1736
+ }
1737
+ },
1738
+ /**
1739
+ * 优先调用原生 resume,否则使用已声明的 load;均不支持则失败。
1740
+ * @param id - 原生会话 ID。
1741
+ * @param cwd - 原会话绝对工作目录。
1742
+ * @param mcpServers - 当前原生 MCP 定义。
1743
+ * @returns 恢复或加载响应;能力缺失时抛错。
1744
+ * @example
1745
+ * ```ts
1746
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1747
+ * await connection.resumeSession('native-id', '/srv/project')
1748
+ * ```
1749
+ */
1750
+ async resumeSession(id, cwd, mcpServers = []) {
1751
+ if (sessionId || sessionOpening)
1752
+ throw new ACPFailure("busy");
1753
+ checkMcp2(mcpServers);
1754
+ sessionId = id;
1755
+ if (agentCapabilities.sessionCapabilities?.resume) {
1756
+ return request(
1757
+ () => connection.agent.request(methods.agent.session.resume, {
1758
+ sessionId: id,
1759
+ cwd,
1760
+ mcpServers
1761
+ })
1762
+ );
1763
+ }
1764
+ if (agentCapabilities.loadSession) {
1765
+ return request(
1766
+ () => connection.agent.request(methods.agent.session.load, {
1767
+ sessionId: id,
1768
+ cwd,
1769
+ mcpServers
1770
+ })
1771
+ );
1772
+ }
1773
+ throw new ACPFailure("unsupported");
1774
+ },
1775
+ /**
1776
+ * 按智能体原生游标列出会话;未声明 list 能力则失败。
1777
+ * @param cursor - 可选分页游标。
1778
+ * @returns 原生分页会话列表。
1779
+ * @example
1780
+ * ```ts
1781
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1782
+ * const page = await connection.listSessions()
1783
+ * ```
1784
+ */
1785
+ async listSessions(cursor) {
1786
+ if (!agentCapabilities.sessionCapabilities?.list)
1787
+ throw new ACPFailure("unsupported");
1788
+ return request(
1789
+ () => connection.agent.request(methods.agent.session.list, { cursor })
1790
+ );
1791
+ },
1792
+ /**
1793
+ * 要求远端删除指定原生会话;不代替框架工作区清理。
1794
+ * @param id - 原生会话 ID。
1795
+ * @returns 原生删除响应。
1796
+ * @example
1797
+ * ```ts
1798
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1799
+ * await connection.deleteSession('native-id')
1800
+ * ```
1801
+ */
1802
+ async deleteSession(id) {
1803
+ if (!agentCapabilities.sessionCapabilities?.delete)
1804
+ throw new ACPFailure("unsupported");
1805
+ return request(
1806
+ () => connection.agent.request(methods.agent.session.delete, {
1807
+ sessionId: id
1808
+ })
1809
+ );
1810
+ },
1811
+ /**
1812
+ * 若远端声明 close 能力则关闭原生会话;进程停止由 close 负责。
1813
+ * @returns 原生关闭完成的 Promise;未声明能力时无需请求。
1814
+ * @example
1815
+ * ```ts
1816
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1817
+ * await connection.closeSession()
1818
+ * ```
1819
+ */
1820
+ async closeSession() {
1821
+ if (!closed && sessionId && agentCapabilities.sessionCapabilities?.close) {
1822
+ await request(
1823
+ () => connection.agent.request(methods.agent.session.close, {
1824
+ sessionId
1825
+ })
1826
+ );
1827
+ }
1828
+ },
1829
+ /**
1830
+ * 给当前原生会话设置智能体公布的模式。
1831
+ * @param modeId - 原生模式 ID。
1832
+ * @returns 原生模式响应。
1833
+ * @example
1834
+ * ```ts
1835
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1836
+ * await connection.setMode('code')
1837
+ * ```
1838
+ */
1839
+ setMode: (modeId) => request(
1840
+ () => connection.agent.request(methods.agent.session.setMode, {
1841
+ sessionId,
1842
+ modeId
1843
+ })
1844
+ ),
1845
+ /**
1846
+ * 给当前原生会话设置智能体公布的配置选项。
1847
+ * @param configId - 原生配置选项 ID。
1848
+ * @param value - 智能体允许的选项值。
1849
+ * @returns 原生配置响应。
1850
+ * @example
1851
+ * ```ts
1852
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1853
+ * await connection.setConfigOption('model', 'coding')
1854
+ * ```
1855
+ */
1856
+ setConfigOption: (configId, value) => request(
1857
+ () => connection.agent.request(methods.agent.session.setConfigOption, {
1858
+ sessionId,
1859
+ configId,
1860
+ value
1861
+ })
1862
+ )
1863
+ };
1864
+ } catch (error) {
1865
+ await close();
1866
+ throw error;
1867
+ }
1868
+ }
1869
+
1870
+ // src/acp/ai-acp-git.ts
1871
+ function createGitOperations(workspace, runtime) {
1872
+ const git = (cwd, args, env) => runtime.run("git", ["-C", cwd, ...args], void 0, {
1873
+ GIT_TERMINAL_PROMPT: "0",
1874
+ ...env
1875
+ });
1876
+ const source = (id) => runtime.path(`main/${id}`);
1877
+ async function ready() {
1878
+ if (!workspace.repositories.length)
1879
+ return;
1880
+ try {
1881
+ await runtime.run("git", ["--version"]);
1882
+ } catch {
1883
+ throw new ACPFailure("gitMissing");
1884
+ }
1885
+ }
1886
+ async function clean(path) {
1887
+ if (await git(path, ["status", "--porcelain"]))
1888
+ throw new ACPFailure("conflict");
1889
+ }
1890
+ async function supportedContent(path) {
1891
+ const index = await git(path, ["ls-files", "--stage", "-z"]);
1892
+ if (index.split("\0").some((entry) => entry.startsWith("160000 ")))
1893
+ throw new ACPFailure("unsupported");
1894
+ const attributes = await git(path, ["ls-files", "-z", "--", ".gitattributes", ":(glob)**/.gitattributes"]);
1895
+ for (const file of attributes.split("\0").filter(Boolean)) {
1896
+ const content = await git(path, ["show", `HEAD:${file}`]);
1897
+ if (content.split("\n").some((line) => !line.trimStart().startsWith("#") && /\bfilter=lfs\b/.test(line)))
1898
+ throw new ACPFailure("unsupported");
1899
+ }
1900
+ }
1901
+ async function checked(repo, path = source(repo.id), branch = repo.mainBranch) {
1902
+ if (await git(path, ["branch", "--show-current"]) !== branch)
1903
+ throw new ACPFailure("conflict");
1904
+ if (await git(path, ["remote", "get-url", repo.remote]) !== repo.url)
1905
+ throw new ACPFailure("conflict");
1906
+ await runtime.file("exists", path);
1907
+ }
1908
+ async function reports(operation) {
1909
+ await ready();
1910
+ const output = [];
1911
+ for (const repo of workspace.repositories) {
1912
+ const report = {
1913
+ repositoryId: repo.id,
1914
+ success: false,
1915
+ phase: "check"
1916
+ };
1917
+ try {
1918
+ await operation(repo, report);
1919
+ report.success = true;
1920
+ report.phase = "done";
1921
+ } catch (error) {
1922
+ report.error = error instanceof ACPFailure ? error.kind : "failed";
1923
+ }
1924
+ output.push(report);
1925
+ }
1926
+ if (output.some((item) => !item.success))
1927
+ throw new ACPFailure("partial", { reports: output });
1928
+ return output;
1929
+ }
1930
+ return {
1931
+ /**
1932
+ * 准备工程主目录,只克隆缺失仓库,不覆盖已有内容。
1933
+ * @returns main 的绝对路径;失败保留已创建文件。
1934
+ * @example
1935
+ * ```ts
1936
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
1937
+ * const main = await git.prepare()
1938
+ * ```
1939
+ */
1940
+ async prepare() {
1941
+ await ready();
1942
+ await runtime.file("mkdir", "main/files");
1943
+ for (const repo of workspace.repositories) {
1944
+ await git(runtime.root, [
1945
+ "check-ref-format",
1946
+ "--branch",
1947
+ repo.mainBranch
1948
+ ]);
1949
+ if (await runtime.file("exists", `main/${repo.id}`) === "false") {
1950
+ await git(runtime.root, [
1951
+ "clone",
1952
+ "--origin",
1953
+ repo.remote,
1954
+ "--branch",
1955
+ repo.mainBranch,
1956
+ "--",
1957
+ repo.url,
1958
+ source(repo.id)
1959
+ ]);
1960
+ }
1961
+ await checked(repo);
1962
+ await supportedContent(source(repo.id));
1963
+ }
1964
+ return runtime.path("main");
1965
+ },
1966
+ /**
1967
+ * 从每个工程本地主分支创建独立工作树并复制普通文件。
1968
+ * @param sessionId - 已校验的框架会话 ID。
1969
+ * @param branch - 所有工程使用的同名新分支。
1970
+ * @returns 独立会话目录;中途失败不回滚已有工作树。
1971
+ * @example
1972
+ * ```ts
1973
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
1974
+ * const directory = await git.createWorktree('task-1', 'acp/task-1')
1975
+ * ```
1976
+ */
1977
+ async createWorktree(sessionId, branch) {
1978
+ await ready();
1979
+ await runtime.file("mkdir", `sessions/${sessionId}`);
1980
+ for (const repo of workspace.repositories) {
1981
+ await checked(repo);
1982
+ await supportedContent(source(repo.id));
1983
+ await git(source(repo.id), ["check-ref-format", "--branch", branch]);
1984
+ await git(source(repo.id), [
1985
+ "worktree",
1986
+ "add",
1987
+ "-b",
1988
+ branch,
1989
+ runtime.path(`sessions/${sessionId}/${repo.id}`),
1990
+ `refs/heads/${repo.mainBranch}`
1991
+ ]);
1992
+ }
1993
+ await runtime.file("copy", "main/files", `sessions/${sessionId}/files`);
1994
+ return runtime.path(`sessions/${sessionId}`);
1995
+ },
1996
+ /**
1997
+ * 更新各工程主分支,只允许快进合并。
1998
+ * @returns 每个工程的更新报告;部分失败抛出携带 reports 的异常。
1999
+ * @example
2000
+ * ```ts
2001
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2002
+ * const reports = await git.updateMain()
2003
+ * ```
2004
+ */
2005
+ updateMain: () => reports(async (repo, report) => {
2006
+ const path = source(repo.id);
2007
+ await checked(repo);
2008
+ await clean(path);
2009
+ report.phase = "fetch";
2010
+ await git(path, [
2011
+ "fetch",
2012
+ "--",
2013
+ repo.remote,
2014
+ `refs/heads/${repo.mainBranch}`
2015
+ ]);
2016
+ report.phase = "merge";
2017
+ await git(path, ["merge", "--ff-only", "FETCH_HEAD"]);
2018
+ report.commit = await git(path, ["rev-parse", "HEAD"]);
2019
+ }),
2020
+ /**
2021
+ * 逐仓提交会话修改并发布,禁止强推。
2022
+ * @param sessionId - 已登记的独立会话 ID。
2023
+ * @param branch - 会话工作树分支名。
2024
+ * @param request - 经过 schema 校验的模式、提交说明及作者。
2025
+ * @returns 各仓发布报告;部分失败保留已提交和已推送的结果。
2026
+ * @example
2027
+ * ```ts
2028
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2029
+ * const reports = await git.publish('task-1', 'acp/task-1', { mode: 'push-branch', message: '完成任务', author: { name: 'Developer', email: 'dev@example.com' } })
2030
+ * ```
2031
+ */
2032
+ publish: (sessionId, branch, request) => reports(async (repo, report) => {
2033
+ const session = runtime.path(`sessions/${sessionId}/${repo.id}`);
2034
+ const main = source(repo.id);
2035
+ const author = {
2036
+ GIT_AUTHOR_NAME: request.author.name,
2037
+ GIT_AUTHOR_EMAIL: request.author.email,
2038
+ GIT_COMMITTER_NAME: request.author.name,
2039
+ GIT_COMMITTER_EMAIL: request.author.email
2040
+ };
2041
+ await checked(repo, session, branch);
2042
+ if (request.mode === "merge-main") {
2043
+ await checked(repo);
2044
+ await clean(main);
2045
+ }
2046
+ report.phase = "commit";
2047
+ if (await git(session, ["status", "--porcelain"])) {
2048
+ await git(session, ["add", "--all"]);
2049
+ await git(session, ["commit", "-m", request.message], author);
2050
+ }
2051
+ report.commit = await git(session, ["rev-parse", "HEAD"]);
2052
+ let target = session;
2053
+ let targetBranch = branch;
2054
+ if (request.mode === "merge-main") {
2055
+ report.phase = "fetch";
2056
+ await git(main, [
2057
+ "fetch",
2058
+ "--",
2059
+ repo.remote,
2060
+ `refs/heads/${repo.mainBranch}`
2061
+ ]);
2062
+ report.phase = "merge";
2063
+ const remoteHead = await git(main, ["rev-parse", "FETCH_HEAD"]);
2064
+ const localHead = await git(main, ["rev-parse", "HEAD"]);
2065
+ const base = await git(main, ["merge-base", "HEAD", "FETCH_HEAD"]);
2066
+ if (base === localHead)
2067
+ await git(main, ["merge", "--ff-only", "FETCH_HEAD"]);
2068
+ else if (base !== remoteHead)
2069
+ throw new ACPFailure("conflict");
2070
+ await git(main, ["merge", "--no-edit", "--", branch], author);
2071
+ target = main;
2072
+ targetBranch = repo.mainBranch;
2073
+ }
2074
+ report.phase = "push";
2075
+ await git(target, [
2076
+ "push",
2077
+ "--",
2078
+ repo.remote,
2079
+ `HEAD:refs/heads/${targetBranch}`
2080
+ ]);
2081
+ report.commit = await git(target, ["rev-parse", "HEAD"]);
2082
+ })
2083
+ };
2084
+ }
2085
+ var fileProgram = String.raw`
2086
+ const fs = require('node:fs/promises'); const path = require('node:path');
2087
+ (async () => {
2088
+ const [root, op, name, content] = process.argv.slice(1);
2089
+ const base = await fs.realpath(root);
2090
+ const target = path.resolve(base, name || '.');
2091
+ const rel = path.relative(base, target);
2092
+ if (rel.startsWith('..') || path.isAbsolute(rel)) throw Error('scope');
2093
+ let cursor = base;
2094
+ for (const part of rel.split(path.sep).filter(Boolean)) {
2095
+ cursor = path.join(cursor, part);
2096
+ try { if ((await fs.lstat(cursor)).isSymbolicLink()) throw Error('link'); }
2097
+ catch (e) { if (e.code !== 'ENOENT') throw e; }
2098
+ }
2099
+ if (op === 'mkdir') await fs.mkdir(target, { recursive: true });
2100
+ else if (op === 'exists') process.stdout.write(String(await fs.stat(target).then(() => true, e => { if(e.code === 'ENOENT') return false; throw e; })));
2101
+ else if (op === 'write') { await fs.mkdir(path.dirname(target), { recursive: true }); await fs.writeFile(target, Buffer.from(content, 'base64'), { flag: 'wx', mode: 0o600 }); }
2102
+ else if (op === 'read') process.stdout.write((await fs.readFile(target)).toString('base64'));
2103
+ else if (op === 'copy') {
2104
+ const destination = path.resolve(base, content);
2105
+ const destRel = path.relative(base, destination);
2106
+ if(destRel.startsWith('..') || path.isAbsolute(destRel)) throw Error('scope');
2107
+ let destCursor = base;
2108
+ for (const part of destRel.split(path.sep).filter(Boolean)) {
2109
+ destCursor = path.join(destCursor, part);
2110
+ try { if ((await fs.lstat(destCursor)).isSymbolicLink()) throw Error('link'); }
2111
+ catch(e) { if(e.code !== 'ENOENT') throw e; }
2112
+ }
2113
+ async function copy(src, dst) {
2114
+ try { if ((await fs.lstat(dst)).isSymbolicLink()) throw Error('link'); }
2115
+ catch(e) { if(e.code !== 'ENOENT') throw e; }
2116
+ const stat = await fs.lstat(src); if(stat.isSymbolicLink()) throw Error('link');
2117
+ if(stat.isDirectory()) { await fs.mkdir(dst, { recursive: true }); for(const item of await fs.readdir(src)) await copy(path.join(src,item),path.join(dst,item)); }
2118
+ else if(stat.isFile()) await fs.copyFile(src,dst,require('node:fs').constants.COPYFILE_EXCL);
2119
+ else throw Error('special file');
2120
+ }
2121
+ await copy(target,destination);
2122
+ }
2123
+ else throw Error('operation');
2124
+ })().catch(() => { process.exitCode = 1 });`;
2125
+ async function selectEngine(preference) {
2126
+ for (const engine of preference === "auto" ? ["podman", "docker"] : [preference]) {
2127
+ try {
2128
+ await execute(engine, ["info"], void 0, void 0, 15e3);
2129
+ return engine;
2130
+ } catch {
2131
+ if (preference !== "auto")
2132
+ throw new ACPFailure("runtime", { engine });
2133
+ }
2134
+ }
2135
+ throw new ACPFailure("runtime");
2136
+ }
2137
+ async function openRuntime(workspace) {
2138
+ const environment = {};
2139
+ for (const [target, source] of Object.entries(workspace.environmentRefs)) {
2140
+ if (!process2.env[source])
2141
+ throw new ACPFailure("invalid", { environment: source });
2142
+ environment[target] = process2.env[source];
2143
+ }
2144
+ let root;
2145
+ let container;
2146
+ let engine;
2147
+ let closed = false;
2148
+ if (workspace.storage.type === "directory") {
2149
+ await mkdir(workspace.storage.path, { recursive: true });
2150
+ root = await realpath(resolve(workspace.storage.path));
2151
+ } else {
2152
+ engine = await selectEngine(workspace.storage.engine);
2153
+ root = "/workspace";
2154
+ container = await execute(engine, [
2155
+ "create",
2156
+ // 远程 Podman 会从服务端继承代理,绕过最小环境白名单;只允许显式 environmentRefs 注入。
2157
+ ...engine === "podman" ? ["--http-proxy=false"] : [],
2158
+ "--init",
2159
+ "--mount",
2160
+ `type=volume,src=${workspace.storage.name},dst=/workspace`,
2161
+ "--mount",
2162
+ `type=volume,src=${workspace.storage.name}-state,dst=/state`,
2163
+ "--workdir",
2164
+ root,
2165
+ "--entrypoint",
2166
+ "node",
2167
+ workspace.storage.image,
2168
+ "-e",
2169
+ "setInterval(() => {}, 2147483647)"
2170
+ ]);
2171
+ if (!/^[a-f0-9]{12,64}$/.test(container))
2172
+ throw new ACPFailure("runtime");
2173
+ try {
2174
+ await execute(engine, ["start", container]);
2175
+ } catch (error) {
2176
+ await execute(engine, ["rm", "-f", container]);
2177
+ throw error;
2178
+ }
2179
+ }
2180
+ const ensure = () => {
2181
+ if (closed)
2182
+ throw new ACPFailure("closed");
2183
+ };
2184
+ const path = (relative2) => workspace.storage.type === "volume" ? `${root}/${relative2}` : resolve(root, relative2);
2185
+ function transport(command, args, cwd, env = {}) {
2186
+ ensure();
2187
+ const values = { ...environment, ...env };
2188
+ return engine && container ? {
2189
+ type: "stdio",
2190
+ command: engine,
2191
+ args: [
2192
+ "exec",
2193
+ "-i",
2194
+ "--workdir",
2195
+ cwd,
2196
+ ...Object.keys(values).flatMap((key) => [
2197
+ "--env",
2198
+ Object.hasOwn(engineEnvironment(), key) ? `${key}=${values[key]}` : key
2199
+ ]),
2200
+ container,
2201
+ command,
2202
+ ...args
2203
+ ],
2204
+ env: { ...values, ...engineEnvironment() }
2205
+ } : { type: "stdio", command, args, cwd, env: values };
2206
+ }
2207
+ async function run(command, args, cwd = root, env) {
2208
+ const launch = transport(command, args, cwd, env);
2209
+ if (launch.type !== "stdio")
2210
+ throw new ACPFailure("runtime");
2211
+ return execute(launch.command, launch.args ?? [], launch.cwd, launch.env);
2212
+ }
2213
+ return {
2214
+ root,
2215
+ path,
2216
+ transport,
2217
+ run,
2218
+ resource: container ? { engine, container } : void 0,
2219
+ /**
2220
+ * 计算受管理的会话状态目录。
2221
+ * @param sessionId - 已校验的会话 ID。
2222
+ * @returns 状态目录绝对路径;不创建目录。
2223
+ * @example
2224
+ * ```ts
2225
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2226
+ * const state = runtime.state('task-1')
2227
+ * ```
2228
+ */
2229
+ state: (sessionId) => workspace.storage.type === "volume" ? `/state/${sessionId}` : path(`.state/${sessionId}`),
2230
+ /**
2231
+ * 执行受工作区约束的文件操作,拒绝链接和路径逃逸。
2232
+ * @param op - mkdir/exists/write/read/copy 操作。
2233
+ * @param name - 工作区内相对路径或已校验的内部绝对路径。
2234
+ * @param content - write 时为 Base64,copy 时为目标路径,其余为空。
2235
+ * @returns 文本结果:exists 为 true/false,read 为 Base64,其余为空。
2236
+ * @example
2237
+ * ```ts
2238
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2239
+ * await runtime.file('mkdir', 'main/files')
2240
+ * ```
2241
+ */
2242
+ async file(op, name, content = "") {
2243
+ return run(
2244
+ workspace.storage.type === "volume" ? "node" : process2.execPath,
2245
+ ["-e", fileProgram, root, op, name, content]
2246
+ );
2247
+ },
2248
+ /**
2249
+ * 向已创建的独立状态目录写入配置,拒绝符号链接。
2250
+ * @param sessionId - 已校验的会话 ID。
2251
+ * @param name - 无目录分隔符的配置文件名。
2252
+ * @param content - 原生配置文本,可能含凭据,不应记录日志。
2253
+ * @returns 完成写入的 Promise。
2254
+ * @example
2255
+ * ```ts
2256
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2257
+ * await runtime.writeState('task-1', 'config.json', '{}')
2258
+ * ```
2259
+ */
2260
+ async writeState(sessionId, name, content) {
2261
+ if (!/^[\w-]+\.[a-z0-9]+$/i.test(name))
2262
+ throw new ACPFailure("invalid");
2263
+ const directory = workspace.storage.type === "volume" ? `/state/${sessionId}` : path(`.state/${sessionId}`);
2264
+ await run(
2265
+ workspace.storage.type === "volume" ? "node" : process2.execPath,
2266
+ [
2267
+ "-e",
2268
+ 'const f=require("node:fs"),p=require("node:path");const d=process.argv[1];let c=p.parse(d).root;for(const part of d.slice(c.length).split(p.sep).filter(Boolean)){c=p.join(c,part);if(f.lstatSync(c).isSymbolicLink())process.exit(1)}const t=p.join(d,process.argv[2]);try{if(f.lstatSync(t).isSymbolicLink())process.exit(1)}catch(e){if(e.code!=="ENOENT")throw e}f.writeFileSync(t,process.argv[3],{mode:384})',
2269
+ directory,
2270
+ name,
2271
+ content
2272
+ ]
2273
+ );
2274
+ },
2275
+ /**
2276
+ * 创建私有状态目录,拒绝祖先目录中的符号链接。
2277
+ * @param sessionId - 已校验的会话 ID。
2278
+ * @returns 已创建的状态目录绝对路径。
2279
+ * @example
2280
+ * ```ts
2281
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2282
+ * const directory = await runtime.stateDirectory('task-1')
2283
+ * ```
2284
+ */
2285
+ async stateDirectory(sessionId) {
2286
+ const target = workspace.storage.type === "volume" ? `/state/${sessionId}` : path(`.state/${sessionId}`);
2287
+ await run(
2288
+ workspace.storage.type === "volume" ? "node" : process2.execPath,
2289
+ [
2290
+ "-e",
2291
+ 'const f=require("node:fs"),p=require("node:path"),d=process.argv[1];let c=p.parse(d).root;for(const part of d.slice(c.length).split(p.sep).filter(Boolean)){c=p.join(c,part);try{if(f.lstatSync(c).isSymbolicLink())process.exit(1)}catch(e){if(e.code!=="ENOENT")throw e;f.mkdirSync(c,{mode:448})}}',
2292
+ target
2293
+ ]
2294
+ );
2295
+ return target;
2296
+ },
2297
+ /**
2298
+ * 关闭本次拥有的容器;目录和 volume 保留。
2299
+ * @returns 清理完成的 Promise;失败抛错,不能视为资源已释放。
2300
+ * @example
2301
+ * ```ts
2302
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2303
+ * await runtime.close()
2304
+ * ```
2305
+ */
2306
+ async close() {
2307
+ if (closed)
2308
+ return;
2309
+ if (engine && container) {
2310
+ try {
2311
+ await execute(engine, ["rm", "--force", container]);
2312
+ } catch {
2313
+ throw new ACPFailure("cleanup", { engine, container });
2314
+ }
2315
+ }
2316
+ closed = true;
2317
+ }
2318
+ };
2319
+ }
2320
+
2321
+ // src/acp/ai-acp-functions.ts
2322
+ function createACPOperations(provider, config, extensions = {}, modelConfig = async () => config, mcpLookup) {
2323
+ const workspaces = provider.createRelStore(
2324
+ "hai_ai_acp_workspaces"
2325
+ );
2326
+ const agents = provider.createRelStore("hai_ai_acp_agents");
2327
+ const sessions = provider.createRelStore(
2328
+ "hai_ai_acp_sessions"
2329
+ );
2330
+ const locks = provider.createRelStore(
2331
+ "hai_ai_acp_locks"
2332
+ );
2333
+ const adapters = { ...nativeAdapters, ...extensions };
2334
+ const live = /* @__PURE__ */ new Map();
2335
+ const connections = /* @__PURE__ */ new Set();
2336
+ const limits = ACPConfigSchema.parse(config.acp ?? {});
2337
+ const pending = /* @__PURE__ */ new Set();
2338
+ const stopping = /* @__PURE__ */ new Map();
2339
+ let closed = false;
2340
+ const agentRegistry = createResourceRegistry(
2341
+ agents,
2342
+ ACPAgentSchema,
2343
+ config.acp?.agents ?? [],
2344
+ () => !closed
2345
+ );
2346
+ const workspaceRegistry = createResourceRegistry(
2347
+ workspaces,
2348
+ ACPWorkspaceSchema,
2349
+ config.acp?.workspaces ?? [],
2350
+ () => !closed
2351
+ );
2352
+ function ensure() {
2353
+ if (closed)
2354
+ throw new ACPFailure("closed");
2355
+ }
2356
+ async function protect(operation) {
2357
+ const task = (async () => {
2358
+ try {
2359
+ ensure();
2360
+ return ok(await operation());
2361
+ } catch (error) {
2362
+ return acpError(error);
2363
+ }
2364
+ })();
2365
+ pending.add(task);
2366
+ try {
2367
+ return await task;
2368
+ } finally {
2369
+ pending.delete(task);
2370
+ }
2371
+ }
2372
+ async function modelSignature(agent) {
2373
+ return createHash("sha256").update(
2374
+ JSON.stringify(
2375
+ resolveAgentModels(agent, await modelConfig()).map(
2376
+ ({ id, config: model }) => ({
2377
+ id,
2378
+ model: model.model,
2379
+ protocol: model.protocol,
2380
+ baseUrl: model.baseUrl,
2381
+ capabilities: model.capabilities
2382
+ })
2383
+ )
2384
+ )
2385
+ ).digest("hex");
2386
+ }
2387
+ async function protectResult(operation) {
2388
+ const result = await protect(operation);
2389
+ return result.success ? result.data : result;
2390
+ }
2391
+ async function getAgent(id) {
2392
+ const entry = await agentRegistry.read(id);
2393
+ if (!entry)
2394
+ throw new ACPFailure("missing", { id });
2395
+ return entry.value;
2396
+ }
2397
+ async function getWorkspace(id) {
2398
+ const entry = await workspaceRegistry.read(id);
2399
+ if (!entry)
2400
+ throw new ACPFailure("missing", { id });
2401
+ const workspace = await normalizeWorkspace(entry.value);
2402
+ await checkWorkspaceOverlap(workspace);
2403
+ return workspace;
2404
+ }
2405
+ const workspaceKey = (workspace) => workspace.storage.type === "directory" ? `directory:${process2.platform === "win32" ? workspace.storage.path.toLowerCase() : workspace.storage.path}` : `volume:${workspace.storage.engine}:${workspace.storage.name}`;
2406
+ async function normalizeWorkspace(workspace) {
2407
+ if (workspace.storage.type === "directory") {
2408
+ await mkdir(workspace.storage.path, { recursive: true });
2409
+ workspace.storage.path = await realpath(resolve(workspace.storage.path));
2410
+ } else {
2411
+ workspace.storage.engine = await selectEngine(workspace.storage.engine);
2412
+ }
2413
+ return workspace;
2414
+ }
2415
+ async function canonicalDirectory(path) {
2416
+ const target = resolve(path);
2417
+ try {
2418
+ return await realpath(target);
2419
+ } catch (error) {
2420
+ if (error.code !== "ENOENT" || dirname(target) === target) {
2421
+ throw error;
2422
+ }
2423
+ return resolve(
2424
+ await canonicalDirectory(dirname(target)),
2425
+ basename(target)
2426
+ );
2427
+ }
2428
+ }
2429
+ async function checkWorkspaceOverlap(workspace) {
2430
+ for (const entry of await workspaceRegistry.values()) {
2431
+ if (entry.id === workspace.id)
2432
+ continue;
2433
+ if (entry.storage.type === "directory" && workspace.storage.type === "directory") {
2434
+ const existing = await canonicalDirectory(entry.storage.path);
2435
+ const child = relative(existing, workspace.storage.path);
2436
+ const parent = relative(workspace.storage.path, existing);
2437
+ if (!child.startsWith("..") && !isAbsolute(child) || !parent.startsWith("..") && !isAbsolute(parent)) {
2438
+ throw new ACPFailure("conflict");
2439
+ }
2440
+ } else if (entry.storage.type === "volume" && workspace.storage.type === "volume" && entry.storage.name === workspace.storage.name && (entry.storage.engine === "auto" || entry.storage.engine === workspace.storage.engine)) {
2441
+ throw new ACPFailure("conflict");
2442
+ }
2443
+ }
2444
+ }
2445
+ async function get(store, id) {
2446
+ sessionIdentitySchema.parse(id);
2447
+ const data = await store.get(id);
2448
+ if (!data)
2449
+ throw new ACPFailure("missing", { id });
2450
+ return data;
2451
+ }
2452
+ async function acquire(key) {
2453
+ const previous = await locks.readVersioned(key);
2454
+ if (previous?.data.owner)
2455
+ throw new ACPFailure("busy");
2456
+ const owner = randomUUID();
2457
+ const version = await locks.compareAndSwap(key, {
2458
+ expectedVersion: previous?.version,
2459
+ data: { owner }
2460
+ });
2461
+ if (!version)
2462
+ throw new ACPFailure("busy");
2463
+ return async () => {
2464
+ if (!await locks.compareAndSwap(key, {
2465
+ expectedVersion: version,
2466
+ data: { owner: null }
2467
+ })) {
2468
+ throw new ACPFailure("cleanup");
2469
+ }
2470
+ };
2471
+ }
2472
+ async function withDefinitions(operation) {
2473
+ const release = await acquire("definition-registry");
2474
+ try {
2475
+ return await operation();
2476
+ } finally {
2477
+ await release();
2478
+ }
2479
+ }
2480
+ async function workspaceOperation(workspace, action) {
2481
+ const release = await acquire(`${workspaceKey(workspace)}:main`);
2482
+ let runtime;
2483
+ try {
2484
+ runtime = await openRuntime(workspace);
2485
+ return await action(runtime);
2486
+ } finally {
2487
+ await runtime?.close();
2488
+ await release();
2489
+ }
2490
+ }
2491
+ async function unique(store, id, data) {
2492
+ if (!await store.compareAndSwap(id, { expectedVersion: void 0, data }))
2493
+ throw new ACPFailure("conflict", { id });
2494
+ return data;
2495
+ }
2496
+ async function validateAgent(agent) {
2497
+ if (agent.mode === "custom-service")
2498
+ return;
2499
+ if (!adapters[agent.adapter])
2500
+ throw new ACPFailure("unsupported", { adapter: agent.adapter });
2501
+ resolveAgentModels(agent, await modelConfig());
2502
+ }
2503
+ async function launchAgent(agent, runtime, info) {
2504
+ if (agent.mode === "custom-service") {
2505
+ return {
2506
+ type: agent.transport,
2507
+ url: agent.url,
2508
+ headers: resolveEnvironment(agent.headerEnv)
2509
+ };
2510
+ }
2511
+ if (!runtime)
2512
+ throw new ACPFailure("missing");
2513
+ const statePath = await runtime.stateDirectory(info.id);
2514
+ const native = await adapters[agent.adapter].configure({
2515
+ agent,
2516
+ models: resolveAgentModels(agent, await modelConfig()),
2517
+ modelId: info.modelId,
2518
+ statePath
2519
+ });
2520
+ for (const [name, content] of Object.entries(native.files ?? {}))
2521
+ await runtime.writeState(info.id, name, content);
2522
+ return runtime.transport(native.command, native.args, info.cwd, {
2523
+ HOME: statePath,
2524
+ USERPROFILE: statePath,
2525
+ ...resolveEnvironment(agent.environmentRefs),
2526
+ ...native.env
2527
+ });
2528
+ }
2529
+ async function stop(id) {
2530
+ const existing = stopping.get(id);
2531
+ if (existing)
2532
+ return existing;
2533
+ const task = stopOne(id);
2534
+ stopping.set(id, task);
2535
+ try {
2536
+ await task;
2537
+ } finally {
2538
+ stopping.delete(id);
2539
+ }
2540
+ }
2541
+ async function stopOne(id) {
2542
+ const current = live.get(id);
2543
+ if (!current)
2544
+ return;
2545
+ await current.connection.cancel();
2546
+ try {
2547
+ await current.connection.closeSession();
2548
+ } catch {
2549
+ } finally {
2550
+ await current.connection.close();
2551
+ await current.runtime?.close();
2552
+ }
2553
+ current.info.status = "closed";
2554
+ current.info.resource = void 0;
2555
+ await sessions.save(id, current.info);
2556
+ await current.release();
2557
+ live.delete(id);
2558
+ }
2559
+ async function start(info, options, resume) {
2560
+ const agent = info.agent;
2561
+ const releaseSession = await acquire(`session:${info.id}`);
2562
+ let releaseDirectory;
2563
+ let runtime;
2564
+ let connection;
2565
+ let recordExists = true;
2566
+ try {
2567
+ const workspace = info.workspaceId ? await getWorkspace(info.workspaceId) : void 0;
2568
+ if (!agent.enabled)
2569
+ throw new ACPFailure("unsupported");
2570
+ if (resume && !await sessions.get(info.id)) {
2571
+ recordExists = false;
2572
+ throw new ACPFailure("missing");
2573
+ }
2574
+ if (workspace) {
2575
+ if (agent.mode === "custom-service")
2576
+ throw new ACPFailure("unsupported");
2577
+ if (agent.mode === "embedded" && workspace.storage.type !== "directory" || agent.mode === "builtin-service" && workspace.storage.type !== "volume") {
2578
+ throw new ACPFailure("unsupported");
2579
+ }
2580
+ if (agent.mode === "builtin-service" && workspace.storage.type === "volume" && (agent.image !== workspace.storage.image || agent.engine !== "auto" && agent.engine !== workspace.storage.engine)) {
2581
+ throw new ACPFailure("conflict");
2582
+ }
2583
+ if (!info.newWorktree)
2584
+ releaseDirectory = await acquire(`${workspaceKey(workspace)}:main`);
2585
+ if (!resume) {
2586
+ const releasePrepare = info.newWorktree ? await acquire(`${workspaceKey(workspace)}:main`) : void 0;
2587
+ try {
2588
+ runtime = await openRuntime(workspace);
2589
+ info.resource = runtime.resource;
2590
+ await sessions.save(info.id, info);
2591
+ const git = createGitOperations(workspace, runtime);
2592
+ await git.prepare();
2593
+ info.cwd = info.newWorktree ? await git.createWorktree(info.id, info.branch) : runtime.path("main");
2594
+ } finally {
2595
+ await releasePrepare?.();
2596
+ }
2597
+ } else {
2598
+ runtime = await openRuntime(workspace);
2599
+ }
2600
+ } else if (agent.mode !== "custom-service" || !info.cwd || info.newWorktree) {
2601
+ throw new ACPFailure("invalid");
2602
+ }
2603
+ ensure();
2604
+ const transport = await launchAgent(agent, runtime, info);
2605
+ connection = await connectACP(transport, options, limits);
2606
+ if (info.mcpServerIds?.length && !mcpLookup)
2607
+ throw new ACPFailure("unsupported");
2608
+ const mcpServers = [
2609
+ ...info.mcpServers,
2610
+ ...info.mcpServerIds?.length && mcpLookup ? await mcpLookup(info.mcpServerIds) : []
2611
+ ];
2612
+ if (resume) {
2613
+ if (!info.nativeId)
2614
+ throw new ACPFailure("unsupported");
2615
+ await connection.resumeSession(info.nativeId, info.cwd, mcpServers);
2616
+ } else {
2617
+ info.nativeId = (await connection.createSession(info.cwd, mcpServers)).sessionId;
2618
+ }
2619
+ ensure();
2620
+ info.status = "active";
2621
+ info.resource = runtime?.resource;
2622
+ await sessions.save(info.id, info);
2623
+ const value = {
2624
+ info,
2625
+ connection,
2626
+ runtime,
2627
+ release: async () => {
2628
+ await releaseDirectory?.();
2629
+ await releaseSession();
2630
+ }
2631
+ };
2632
+ live.set(info.id, value);
2633
+ return handle(info.id, value);
2634
+ } catch (error) {
2635
+ await connection?.close();
2636
+ await runtime?.close();
2637
+ info.status = "failed";
2638
+ info.resource = void 0;
2639
+ if (recordExists)
2640
+ await sessions.save(info.id, info);
2641
+ await releaseDirectory?.();
2642
+ await releaseSession();
2643
+ throw error;
2644
+ }
2645
+ }
2646
+ function handle(id, expected) {
2647
+ function current() {
2648
+ ensure();
2649
+ const value = live.get(id);
2650
+ if (!value || expected && value !== expected)
2651
+ throw new ACPFailure("closed");
2652
+ return value;
2653
+ }
2654
+ async function gitOperation(publish) {
2655
+ const request = publish ? ACPPublishSchema.parse(publish) : void 0;
2656
+ const info = await get(sessions, id);
2657
+ if (!info.workspaceId || request && !info.newWorktree)
2658
+ throw new ACPFailure("unsupported");
2659
+ const workspace = await getWorkspace(info.workspaceId);
2660
+ await stop(id);
2661
+ const release = await acquire(`session:${id}`);
2662
+ try {
2663
+ return await workspaceOperation(workspace, (runtime) => request ? createGitOperations(workspace, runtime).publish(
2664
+ id,
2665
+ info.branch,
2666
+ request
2667
+ ) : createGitOperations(workspace, runtime).updateMain());
2668
+ } finally {
2669
+ await release();
2670
+ }
2671
+ }
2672
+ return {
2673
+ id,
2674
+ /**
2675
+ * 读取持久会话信息,包括状态、原生 ID 和当前容器。
2676
+ * @returns 包含 ACPSessionInfo 的 HaiResult;记录不存在时返回失败。
2677
+ * @example
2678
+ * ```ts
2679
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2680
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2681
+ * const result = await session.info()
2682
+ * ```
2683
+ */
2684
+ info: () => protect(() => get(sessions, id)),
2685
+ /**
2686
+ * 发送一轮内容,等待最终停止原因、最终消息文本和完整事件;同一会话不允许并发轮次。
2687
+ * @param content - 原生 ACP 内容块,不允许同一会话并发执行。
2688
+ * @param signal - 本轮取消信号;取消超时会关闭连接。
2689
+ * @returns 最终文本、停止原因和事件的 HaiResult。
2690
+ * @example
2691
+ * ```ts
2692
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2693
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2694
+ * const result = await session.prompt([{ type: 'text', text: '检查工程' }])
2695
+ * ```
2696
+ */
2697
+ prompt: (content, signal) => protect(() => current().connection.prompt(content, signal)),
2698
+ /**
2699
+ * 仅接受正常结束的一份 JSON(允许完整 JSON 代码围栏),再按 schema 校验;前后夹杂文本视为失败。
2700
+ * @param content - 要求返回结构化 JSON 的原生内容块。
2701
+ * @param schema - 最终 JSON 的 Zod 校验器。
2702
+ * @param signal - 可选的本轮取消信号。
2703
+ * @returns 经过 schema 校验的数据;输出不合法时返回 ACP_OUTPUT_INVALID。
2704
+ * @example
2705
+ * ```ts
2706
+ * import { z } from 'zod'
2707
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2708
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2709
+ * const result = await session.promptStructured([{ type: 'text', text: '返回 JSON' }], z.object({ ok: z.boolean() }))
2710
+ * ```
2711
+ */
2712
+ promptStructured: (content, schema, signal) => protect(async () => {
2713
+ const response = await current().connection.prompt(content, signal);
2714
+ if (response.stopReason !== "end_turn")
2715
+ throw new ACPFailure("output");
2716
+ let text = response.text.trim();
2717
+ const fenced = /^```(?:json)?[ \t]*\n([\s\S]*?)\n```$/.exec(text);
2718
+ if (fenced)
2719
+ text = fenced[1];
2720
+ try {
2721
+ return schema.parse(JSON.parse(text));
2722
+ } catch {
2723
+ throw new ACPFailure("output");
2724
+ }
2725
+ }),
2726
+ /**
2727
+ * 迭代本轮 ACP 事件;中途退出迭代会取消本轮,失败会抛错,调用方需 try/finally 清理。
2728
+ * @param content - 本轮原生内容块。
2729
+ * @param signal - 可选取消信号;结束迭代同样会取消本轮。
2730
+ * @returns 异步事件迭代器;流式失败抛出异常,需在调用方捕获。
2731
+ * @throws 连接关闭、取消超时、输出超限或原生请求失败。
2732
+ * @example
2733
+ * ```ts
2734
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2735
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2736
+ * for await (const event of session.stream([{ type: 'text', text: '检查工程' }])) { const update = event.update }
2737
+ * ```
2738
+ */
2739
+ async *stream(content, signal) {
2740
+ const value = current();
2741
+ const controller = new AbortController();
2742
+ const queue = [];
2743
+ let wake;
2744
+ const completion = { done: false };
2745
+ let error;
2746
+ const unsubscribe = value.connection.subscribe((event) => {
2747
+ if (queue.length >= limits.maxPendingEvents)
2748
+ throw new ACPFailure("output");
2749
+ queue.push(event);
2750
+ wake?.();
2751
+ });
2752
+ const task = value.connection.prompt(
2753
+ content,
2754
+ signal ? AbortSignal.any([signal, controller.signal]) : controller.signal
2755
+ ).catch((reason) => {
2756
+ error = reason;
2757
+ }).finally(() => {
2758
+ completion.done = true;
2759
+ wake?.();
2760
+ });
2761
+ try {
2762
+ while (!completion.done || queue.length) {
2763
+ if (queue.length) {
2764
+ yield queue.shift();
2765
+ } else {
2766
+ await new Promise((resolve3) => {
2767
+ wake = resolve3;
2768
+ });
2769
+ }
2770
+ }
2771
+ if (error)
2772
+ throw error;
2773
+ } finally {
2774
+ unsubscribe();
2775
+ controller.abort();
2776
+ await task;
2777
+ }
2778
+ },
2779
+ /**
2780
+ * 请求取消正在执行的一轮;超时未停止时关闭所拥有的传输。
2781
+ * @returns 取消完成的 HaiResult;超时或清理失败返回错误。
2782
+ * @example
2783
+ * ```ts
2784
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2785
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2786
+ * const result = await session.cancel()
2787
+ * ```
2788
+ */
2789
+ cancel: () => protect(() => current().connection.cancel()),
2790
+ /**
2791
+ * 选择智能体公布的原生模式 ID;不兼容时返回协议失败。
2792
+ * @param mode - 握手或事件中公布的原生模式 ID。
2793
+ * @returns 原生模式设置结果的 HaiResult。
2794
+ * @example
2795
+ * ```ts
2796
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2797
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2798
+ * const result = await session.setMode('code')
2799
+ * ```
2800
+ */
2801
+ setMode: (mode) => protect(() => current().connection.setMode(mode)),
2802
+ /**
2803
+ * 设置智能体公布的原生配置选项;值必须符合该智能体的选项定义。
2804
+ * @param key - 原生配置选项 ID。
2805
+ * @param value - 该选项允许的值。
2806
+ * @returns 原生配置更新结果的 HaiResult。
2807
+ * @example
2808
+ * ```ts
2809
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2810
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2811
+ * const result = await session.setConfigOption('model', 'coding')
2812
+ * ```
2813
+ */
2814
+ setConfigOption: (key, value) => protect(() => current().connection.setConfigOption(key, value)),
2815
+ /**
2816
+ * 先关闭会话执行环境,再 fetch 并仅快进各工程明确配置的主分支;不修改会话分支。
2817
+ * @returns 各仓更新报告;部分失败时报告位于 error.cause.reports。
2818
+ * @example
2819
+ * ```ts
2820
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2821
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2822
+ * const result = await session.updateMain()
2823
+ * ```
2824
+ */
2825
+ updateMain: () => protect(() => gitOperation()),
2826
+ /**
2827
+ * 关闭执行环境后逐仓提交并推送;仅支持受管理的独立会话,部分失败保留各仓报告,禁止强推。
2828
+ * @param request - 发布模式、提交说明和作者;仅支持受管理的独立会话。
2829
+ * @returns 各仓提交/推送报告;部分失败时报告位于 error.cause.reports。
2830
+ * @example
2831
+ * ```ts
2832
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2833
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2834
+ * const result = await session.publish({ mode: 'merge-main', message: '完成任务', author: { name: 'Developer', email: 'dev@example.com' } })
2835
+ * ```
2836
+ */
2837
+ publish: (request) => protect(() => gitOperation(request)),
2838
+ /**
2839
+ * 停止拥有的进程或容器,释放占用;保留工作目录和原生状态,供后续显式恢复。
2840
+ * @returns 关闭完成的 HaiResult;清理失败时不释放仍在占用的资源。
2841
+ * @example
2842
+ * ```ts
2843
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2844
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2845
+ * const result = await session.close()
2846
+ * ```
2847
+ */
2848
+ close: () => protect(() => stop(id))
2849
+ };
2850
+ }
2851
+ return {
2852
+ agents: {
2853
+ /**
2854
+ * 校验模型与适配器后新增数据库智能体;配置同名条目只读。
2855
+ * @param input - 完整智能体定义,必须引用已有 Hai 模型 ID。
2856
+ * @returns 带 source/version 的新增条目;配置同 ID 返回只读错误。
2857
+ * @example
2858
+ * ```ts
2859
+ * import { ai } from '@h-ai/ai'
2860
+ * // 前提:ai 已成功初始化。
2861
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2862
+ * const result = await ai.acp.agents.register({ id: 'coder', name: 'Coder', mode: 'embedded', adapter: 'codex', modelIds: ['coding'] })
2863
+ * ```
2864
+ */
2865
+ register: (input) => protectResult(async () => {
2866
+ const agent = ACPAgentSchema.parse(input);
2867
+ await validateAgent(agent);
2868
+ return agentRegistry.operations.register(agent);
2869
+ }),
2870
+ /**
2871
+ * 按 ID 读取合并清单条目;source 区分配置与数据库,数据库 get 返回 version。
2872
+ * @param id - 智能体 ID。
2873
+ * @returns 合并后的资源条目;未找到时返回资源不存在错误。
2874
+ * @example
2875
+ * ```ts
2876
+ * import { ai } from '@h-ai/ai'
2877
+ * // 前提:ai 已成功初始化。
2878
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2879
+ * const result = await ai.acp.agents.get('coder')
2880
+ * ```
2881
+ */
2882
+ get: agentRegistry.operations.get,
2883
+ /**
2884
+ * 列出配置与数据库合并后的智能体;同 ID 配置优先。
2885
+ * @returns 去重后的智能体条目列表;列表项不带数据库 version。
2886
+ * @example
2887
+ * ```ts
2888
+ * import { ai } from '@h-ai/ai'
2889
+ * // 前提:ai 已成功初始化。
2890
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2891
+ * const result = await ai.acp.agents.list()
2892
+ * ```
2893
+ */
2894
+ list: agentRegistry.operations.list,
2895
+ /**
2896
+ * 整体替换数据库智能体;可传入 get 返回的版本防止覆盖并发修改,配置条目不可改。
2897
+ * @param input - 包含原 ID 的完整替换定义。
2898
+ * @param version - get 返回的数据库版本;传入时校验并发修改。
2899
+ * @returns 更新后的资源条目;配置只读、版本冲突均返回失败。
2900
+ * @example
2901
+ * ```ts
2902
+ * import { ai } from '@h-ai/ai'
2903
+ * // 前提:ai 已成功初始化。
2904
+ * // definition 为完整替换定义;entry 为 get 成功返回的条目。
2905
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2906
+ * const result = await ai.acp.agents.update(definition, entry.version)
2907
+ * ```
2908
+ */
2909
+ update: (input, version) => protectResult(async () => {
2910
+ const agent = ACPAgentSchema.parse(input);
2911
+ await validateAgent(agent);
2912
+ return agentRegistry.operations.update(agent, version);
2913
+ }),
2914
+ /**
2915
+ * 删除无会话引用的数据库智能体定义;不删除镜像或外部服务。
2916
+ * @param id - 数据库智能体 ID。
2917
+ * @param version - 可选的期望版本。
2918
+ * @returns 删除完成的 HaiResult;存在会话引用时返回 ACP_BUSY。
2919
+ * @example
2920
+ * ```ts
2921
+ * import { ai } from '@h-ai/ai'
2922
+ * // 前提:ai 已成功初始化。
2923
+ * // entry 为 get 成功返回的数据库条目。
2924
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2925
+ * const result = await ai.acp.agents.remove('coder', entry.version)
2926
+ * ```
2927
+ */
2928
+ remove: (id, version) => protectResult(async () => {
2929
+ return withDefinitions(async () => {
2930
+ const existing = await agentRegistry.read(id);
2931
+ if (existing?.source === "config")
2932
+ return agentRegistry.operations.remove(id, version);
2933
+ if ((await sessions.query({ where: { agentId: id } })).length)
2934
+ throw new ACPFailure("busy");
2935
+ return agentRegistry.operations.remove(id, version);
2936
+ });
2937
+ }),
2938
+ /**
2939
+ * 解析模型和原生配置,验证适配器是否兼容;不持久保存模型凭据。
2940
+ * @param id - 已登记智能体 ID。
2941
+ * @param modelId - 可选模型 ID,须在智能体 modelIds 中。
2942
+ * @returns 智能体模式与绑定模型摘要;不返回凭据或原生配置内容。
2943
+ * @example
2944
+ * ```ts
2945
+ * import { ai } from '@h-ai/ai'
2946
+ * // 前提:ai 已成功初始化。
2947
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2948
+ * const result = await ai.acp.agents.initialize('coder')
2949
+ * ```
2950
+ */
2951
+ initialize: (id, modelId) => protect(async () => {
2952
+ const agent = await getAgent(id);
2953
+ await validateAgent(agent);
2954
+ if (agent.mode === "custom-service")
2955
+ return { agentId: id, mode: agent.mode, modelIds: [] };
2956
+ await adapters[agent.adapter].configure({
2957
+ agent,
2958
+ models: resolveAgentModels(agent, await modelConfig()),
2959
+ modelId: modelId ?? agent.defaultModelId ?? agent.modelIds[0],
2960
+ statePath: "/state/validation"
2961
+ });
2962
+ return { agentId: id, mode: agent.mode, modelIds: agent.modelIds };
2963
+ }),
2964
+ /**
2965
+ * 实际启动并握手后关闭连接,返回智能体能力;托管模式需指定工作区。
2966
+ * @param id - 已登记智能体 ID。
2967
+ * @param options - 回调、能力及工作区;托管模式必须传 workspaceId。
2968
+ * @returns 原生初始化结果(智能体能力、认证方法等)的 HaiResult。
2969
+ * @example
2970
+ * ```ts
2971
+ * import { ai } from '@h-ai/ai'
2972
+ * // 前提:ai 已成功初始化。
2973
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2974
+ * const result = await ai.acp.agents.check('coder', { workspaceId: 'project' })
2975
+ * ```
2976
+ */
2977
+ check: (id, options = {}) => protect(async () => {
2978
+ const agent = await getAgent(id);
2979
+ await validateAgent(agent);
2980
+ if (agent.mode !== "custom-service") {
2981
+ if (!options.workspaceId)
2982
+ throw new ACPFailure("invalid");
2983
+ const workspace = await getWorkspace(options.workspaceId);
2984
+ if (agent.mode === "embedded" && workspace.storage.type !== "directory" || agent.mode === "builtin-service" && (workspace.storage.type !== "volume" || workspace.storage.image !== agent.image)) {
2985
+ throw new ACPFailure("unsupported");
2986
+ }
2987
+ return workspaceOperation(workspace, async (runtime) => {
2988
+ const info = {
2989
+ id: randomUUID(),
2990
+ agentId: id,
2991
+ cwd: runtime.root,
2992
+ newWorktree: false,
2993
+ agent,
2994
+ status: "opening",
2995
+ mcpServers: [],
2996
+ modelId: agent.defaultModelId ?? agent.modelIds[0]
2997
+ };
2998
+ const connection2 = await connectACP(
2999
+ await launchAgent(agent, runtime, info),
3000
+ options,
3001
+ limits
3002
+ );
3003
+ try {
3004
+ return connection2.initialized;
3005
+ } finally {
3006
+ await connection2.close();
3007
+ }
3008
+ });
3009
+ }
3010
+ const connection = await connectACP(
3011
+ {
3012
+ type: agent.transport,
3013
+ url: agent.url,
3014
+ headers: resolveEnvironment(agent.headerEnv)
3015
+ },
3016
+ options,
3017
+ limits
3018
+ );
3019
+ try {
3020
+ return connection.initialized;
3021
+ } finally {
3022
+ await connection.close();
3023
+ }
3024
+ })
3025
+ },
3026
+ workspaces: {
3027
+ /**
3028
+ * 新增数据库工作区并归一化存储位置;拒绝目录重叠或同一 volume 重复登记。
3029
+ * @param input - 工作区存储位置、显式 Git 主分支和环境变量引用。
3030
+ * @returns 带来源和版本的工作区条目;重叠位置返回冲突错误。
3031
+ * @example
3032
+ * ```ts
3033
+ * import { ai } from '@h-ai/ai'
3034
+ * // 前提:ai 已成功初始化。
3035
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3036
+ * const result = await ai.acp.workspaces.register({ id: 'project', storage: { type: 'directory', path: '/srv/project' } })
3037
+ * ```
3038
+ */
3039
+ register: (input) => protectResult(async () => {
3040
+ const workspace = ACPWorkspaceSchema.parse(input);
3041
+ if ((await workspaceRegistry.read(workspace.id))?.source === "config")
3042
+ return workspaceRegistry.operations.register(workspace);
3043
+ await normalizeWorkspace(workspace);
3044
+ const release = await acquire("definition-registry");
3045
+ try {
3046
+ await checkWorkspaceOverlap(workspace);
3047
+ return await workspaceRegistry.operations.register(workspace);
3048
+ } finally {
3049
+ await release();
3050
+ }
3051
+ }),
3052
+ /**
3053
+ * 读取工作区定义及来源;配置条目无数据库版本。
3054
+ * @param id - 工作区 ID。
3055
+ * @returns 工作区资源条目;未找到时返回资源不存在错误。
3056
+ * @example
3057
+ * ```ts
3058
+ * import { ai } from '@h-ai/ai'
3059
+ * // 前提:ai 已成功初始化。
3060
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3061
+ * const result = await ai.acp.workspaces.get('project')
3062
+ * ```
3063
+ */
3064
+ get: workspaceRegistry.operations.get,
3065
+ /**
3066
+ * 列出配置与数据库合并后的工作区;不创建路径或克隆工程。
3067
+ * @returns 去重后的工作区列表;列表项不含数据库 version。
3068
+ * @example
3069
+ * ```ts
3070
+ * import { ai } from '@h-ai/ai'
3071
+ * // 前提:ai 已成功初始化。
3072
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3073
+ * const result = await ai.acp.workspaces.list()
3074
+ * ```
3075
+ */
3076
+ list: workspaceRegistry.operations.list,
3077
+ /**
3078
+ * 整体替换无会话引用的数据库工作区,重新检查存储位置重叠;配置条目不可修改。
3079
+ * @param input - 包含原 ID 的完整替换定义。
3080
+ * @param version - 可选的数据库期望版本。
3081
+ * @returns 更新后的工作区;有会话引用、位置冲突或版本冲突时失败。
3082
+ * @example
3083
+ * ```ts
3084
+ * import { ai } from '@h-ai/ai'
3085
+ * // 前提:ai 已成功初始化。
3086
+ * // definition 为完整替换定义;entry 为 get 成功返回的条目。
3087
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3088
+ * const result = await ai.acp.workspaces.update(definition, entry.version)
3089
+ * ```
3090
+ */
3091
+ update: (input, version) => protectResult(async () => {
3092
+ const workspace = ACPWorkspaceSchema.parse(input);
3093
+ const existing = await workspaceRegistry.read(workspace.id);
3094
+ if (existing?.source === "config")
3095
+ return workspaceRegistry.operations.update(workspace, version);
3096
+ const release = await acquire("definition-registry");
3097
+ try {
3098
+ if ((await sessions.query({ where: { workspaceId: workspace.id } })).length) {
3099
+ throw new ACPFailure("busy");
3100
+ }
3101
+ await normalizeWorkspace(workspace);
3102
+ await checkWorkspaceOverlap(workspace);
3103
+ return await workspaceRegistry.operations.update(
3104
+ workspace,
3105
+ version
3106
+ );
3107
+ } finally {
3108
+ await release();
3109
+ }
3110
+ }),
3111
+ /**
3112
+ * 创建目录并克隆缺失工程;已有内容不自动 pull 或覆盖,Git 不存在时返回明确错误。
3113
+ * @param id - 已登记工作区 ID。
3114
+ * @returns 准备后的 main 绝对目录;克隆失败保留现场供检查。
3115
+ * @example
3116
+ * ```ts
3117
+ * import { ai } from '@h-ai/ai'
3118
+ * // 前提:ai 已成功初始化。
3119
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3120
+ * const result = await ai.acp.workspaces.prepare('project')
3121
+ * ```
3122
+ */
3123
+ prepare: (id) => protect(
3124
+ async () => workspaceOperation(await getWorkspace(id), (runtime) => createGitOperationsInput(id, runtime))
3125
+ ),
3126
+ /**
3127
+ * 将有限大小的文件写入 main/files,拒绝覆盖、路径逃逸和符号链接;失败前已写入文件会保留。
3128
+ * @param id - 已登记工作区 ID。
3129
+ * @param files - 相对路径和 UTF-8 文本或二进制;不允许覆盖或路径逃逸。
3130
+ * @returns 已导入相对路径列表;写入中途失败不回滚已有文件。
3131
+ * @example
3132
+ * ```ts
3133
+ * import { ai } from '@h-ai/ai'
3134
+ * // 前提:ai 已成功初始化。
3135
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3136
+ * const result = await ai.acp.workspaces.importFiles('project', [{ path: 'notes.txt', content: '需求' }])
3137
+ * ```
3138
+ */
3139
+ importFiles: (id, files) => protect(async () => {
3140
+ if (files.length > 1e3 || files.reduce(
3141
+ (sum, file) => sum + Buffer.byteLength(file.content),
3142
+ 0
3143
+ ) > limits.maxContentBytes) {
3144
+ throw new ACPFailure("output");
3145
+ }
3146
+ for (const file of files) {
3147
+ if (!file.path || file.path.split(/[\\/]/).some(
3148
+ (part) => !part || part === "." || part === ".." || part === ".git"
3149
+ ) || isAbsolute(file.path)) {
3150
+ throw new ACPFailure("invalid");
3151
+ }
3152
+ }
3153
+ return workspaceOperation(await getWorkspace(id), async (runtime) => {
3154
+ await runtime.file("mkdir", "main/files");
3155
+ for (const file of files) {
3156
+ if (await runtime.file("exists", `main/files/${file.path}`) === "true") {
3157
+ throw new ACPFailure("conflict");
3158
+ }
3159
+ }
3160
+ for (const file of files) {
3161
+ await runtime.file(
3162
+ "write",
3163
+ `main/files/${file.path}`,
3164
+ Buffer.from(file.content).toString("base64")
3165
+ );
3166
+ }
3167
+ return { imported: files.map((file) => file.path) };
3168
+ });
3169
+ }),
3170
+ /**
3171
+ * 移除无会话引用的数据库定义,保留磁盘目录、Git 检出和 volume。
3172
+ * @param id - 数据库工作区 ID。
3173
+ * @param version - 可选的数据库期望版本。
3174
+ * @returns 删除定义的 HaiResult;引用存在时失败,文件始终保留。
3175
+ * @example
3176
+ * ```ts
3177
+ * import { ai } from '@h-ai/ai'
3178
+ * // 前提:ai 已成功初始化。
3179
+ * // entry 为 get 成功返回的数据库条目。
3180
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3181
+ * const result = await ai.acp.workspaces.remove('project', entry.version)
3182
+ * ```
3183
+ */
3184
+ remove: (id, version) => protectResult(async () => {
3185
+ return withDefinitions(async () => {
3186
+ const existing = await workspaceRegistry.read(id);
3187
+ if (existing?.source === "config")
3188
+ return workspaceRegistry.operations.remove(id, version);
3189
+ if ((await sessions.query({ where: { workspaceId: id } })).length)
3190
+ throw new ACPFailure("busy");
3191
+ return workspaceRegistry.operations.remove(id, version);
3192
+ });
3193
+ })
3194
+ },
3195
+ sessions: {
3196
+ /**
3197
+ * 绑定模型、工作区和 MCP,准备目录后建立原生会话;newWorktree 可选择独立工程分支。
3198
+ * @param options - 智能体、工作区、模型、MCP 及可信回调;newWorktree 默认 false。
3199
+ * @returns 可用于 prompt/stream/publish 的会话句柄;启动失败保留失败记录。
3200
+ * @example
3201
+ * ```ts
3202
+ * import { ai } from '@h-ai/ai'
3203
+ * // 前提:ai 已成功初始化。
3204
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3205
+ * const result = await ai.acp.sessions.create({ agentId: 'coder', workspaceId: 'project', newWorktree: true })
3206
+ * ```
3207
+ */
3208
+ create: (options) => protect(async () => {
3209
+ const info = await withDefinitions(async () => {
3210
+ const agent = await getAgent(options.agentId);
3211
+ await validateAgent(agent);
3212
+ if (!agent.enabled)
3213
+ throw new ACPFailure("unsupported");
3214
+ const id = sessionIdentitySchema.parse(options.id ?? randomUUID());
3215
+ const modelId = options.modelId ?? agent.defaultModelId ?? agent.modelIds[0];
3216
+ if (modelId && !agent.modelIds.includes(modelId))
3217
+ throw new ACPFailure("model");
3218
+ const info2 = {
3219
+ id,
3220
+ agentId: agent.id,
3221
+ workspaceId: options.workspaceId,
3222
+ cwd: options.cwd ?? "",
3223
+ newWorktree: options.newWorktree ?? false,
3224
+ branch: options.newWorktree ? branchNameSchema.parse(options.branch ?? `acp/${id}`) : void 0,
3225
+ modelId,
3226
+ modelSignature: await modelSignature(agent),
3227
+ status: "opening",
3228
+ agent,
3229
+ mcpServers: options.mcpServers ?? [],
3230
+ mcpServerIds: options.mcpServerIds
3231
+ };
3232
+ if (options.workspaceId)
3233
+ await getWorkspace(options.workspaceId);
3234
+ await unique(sessions, id, info2);
3235
+ return info2;
3236
+ });
3237
+ return start(info, options, false);
3238
+ }),
3239
+ /**
3240
+ * 获取持久会话的管理句柄;已关闭会话需要 resume 后才能执行 prompt。
3241
+ * @param id - 框架会话 ID,不是智能体原生 ID。
3242
+ * @returns 管理句柄的 HaiResult;不会自动重新启动智能体。
3243
+ * @example
3244
+ * ```ts
3245
+ * import { ai } from '@h-ai/ai'
3246
+ * // 前提:ai 已成功初始化。
3247
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3248
+ * const result = await ai.acp.sessions.get('task-1')
3249
+ * ```
3250
+ */
3251
+ get: (id) => protect(async () => {
3252
+ await get(sessions, id);
3253
+ return handle(id);
3254
+ }),
3255
+ /**
3256
+ * 列出持久会话,可按工作区 ID 过滤。
3257
+ * @param workspaceId - 可选工作区筛选条件;省略返回全部会话。
3258
+ * @returns 持久 ACPSessionInfo 列表的 HaiResult。
3259
+ * @example
3260
+ * ```ts
3261
+ * import { ai } from '@h-ai/ai'
3262
+ * // 前提:ai 已成功初始化。
3263
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3264
+ * const result = await ai.acp.sessions.list('project')
3265
+ * ```
3266
+ */
3267
+ list: (workspaceId) => protect(
3268
+ () => sessions.query({ where: workspaceId ? { workspaceId } : {} })
3269
+ ),
3270
+ /**
3271
+ * 使用原生会话 ID 和保留状态恢复;智能体定义或模型能力已改变时拒绝恢复。
3272
+ * @param id - 已关闭或失败的框架会话 ID。
3273
+ * @param options - 本次连接的回调、能力和取消信号,不从数据库恢复回调。
3274
+ * @returns 恢复后的会话句柄;原生服务不支持恢复或配置变化时失败。
3275
+ * @example
3276
+ * ```ts
3277
+ * import { ai } from '@h-ai/ai'
3278
+ * // 前提:ai 已成功初始化。
3279
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3280
+ * const result = await ai.acp.sessions.resume('task-1')
3281
+ * ```
3282
+ */
3283
+ resume: (id, options = {}) => protect(async () => {
3284
+ const info = await get(sessions, id);
3285
+ if (info.status === "opening" || info.status === "active")
3286
+ throw new ACPFailure("busy");
3287
+ const agent = await getAgent(info.agentId);
3288
+ if (!agent.enabled || JSON.stringify(agent) !== JSON.stringify(info.agent)) {
3289
+ throw new ACPFailure("conflict");
3290
+ }
3291
+ if (info.modelSignature !== await modelSignature(agent))
3292
+ throw new ACPFailure("conflict");
3293
+ return start(info, options, true);
3294
+ }),
3295
+ /**
3296
+ * 关闭本进程拥有的会话并释放锁,不删除历史和工程内容。
3297
+ * @param id - 框架会话 ID。
3298
+ * @returns 关闭完成的 HaiResult;不允许关闭其他进程拥有的执行环境。
3299
+ * @example
3300
+ * ```ts
3301
+ * import { ai } from '@h-ai/ai'
3302
+ * // 前提:ai 已成功初始化。
3303
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3304
+ * const result = await ai.acp.sessions.close('task-1')
3305
+ * ```
3306
+ */
3307
+ close: (id) => protect(() => stop(id)),
3308
+ /**
3309
+ * 仅删除已关闭或失败的会话记录;不删除工作树及原生文件,不替代 Git worktree 清理。
3310
+ * @param id - 框架会话 ID。
3311
+ * @returns 删除完成的 HaiResult;活动会话返回忙碌错误。
3312
+ * @example
3313
+ * ```ts
3314
+ * import { ai } from '@h-ai/ai'
3315
+ * // 前提:ai 已成功初始化。
3316
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3317
+ * const result = await ai.acp.sessions.remove('task-1')
3318
+ * ```
3319
+ */
3320
+ remove: (id) => protect(async () => {
3321
+ const info = await get(sessions, id);
3322
+ if (live.has(id) || info.status === "active" || info.status === "opening") {
3323
+ throw new ACPFailure("busy");
3324
+ }
3325
+ const release = await acquire(`session:${id}`);
3326
+ try {
3327
+ await sessions.remove(id);
3328
+ } finally {
3329
+ await release();
3330
+ }
3331
+ })
3332
+ },
3333
+ /**
3334
+ * 连接原生 stdio/HTTP/WS 服务;不创建工作区或提供传输兼容桥。
3335
+ * @param transport - 智能体实际支持的原生传输与命令或地址。
3336
+ * @param options - 可信回调、声明能力和初始化取消信号。
3337
+ * @returns 低层连接句柄的 HaiResult;连接失败不自动转换传输。
3338
+ * @example
3339
+ * ```ts
3340
+ * import { ai } from '@h-ai/ai'
3341
+ * // 前提:ai 已成功初始化。
3342
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3343
+ * const result = await ai.acp.connect({ type: 'http', url: 'https://agent.example.com/acp' })
3344
+ * ```
3345
+ */
3346
+ connect: (transport, options = {}) => protect(async () => {
3347
+ const connection = await connectACP(transport, options, limits);
3348
+ connections.add(connection);
3349
+ return {
3350
+ info: connection.initialized,
3351
+ /**
3352
+ * 选择对端握手公布的认证方法,返回 HaiResult。
3353
+ * @param method - initialize 返回的认证方法 ID。
3354
+ * @returns 认证响应的 HaiResult。
3355
+ * @example
3356
+ * ```ts
3357
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3358
+ * const result = await connection.authenticate('api-key')
3359
+ * ```
3360
+ */
3361
+ authenticate: (method) => protect(() => connection.authenticate(method)),
3362
+ /**
3363
+ * 在对端可见的绝对 cwd 创建原生会话,返回 prompt/cancel 句柄。
3364
+ * @param cwd - 对端文件系统可见的绝对工作目录。
3365
+ * @param mcpServers - 可选原生 MCP 定义;调用方负责凭据与授权。
3366
+ * @returns 原生会话句柄;不创建框架持久会话记录。
3367
+ * @example
3368
+ * ```ts
3369
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3370
+ * const result = await connection.createSession('/srv/project', [])
3371
+ * ```
3372
+ */
3373
+ createSession: (cwd, mcpServers) => protect(async () => {
3374
+ const session = await connection.createSession(cwd, mcpServers);
3375
+ return {
3376
+ id: session.sessionId,
3377
+ /**
3378
+ * 发送一轮原生内容,返回最终文本、停止原因和事件。
3379
+ * @param content - 原生 ACP 内容块。
3380
+ * @param signal - 可选本轮取消信号。
3381
+ * @returns 最终文本、停止原因和事件的 HaiResult。
3382
+ * @example
3383
+ * ```ts
3384
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3385
+ * const result = await session.prompt([{ type: 'text', text: '检查工程' }])
3386
+ * ```
3387
+ */
3388
+ prompt: (content, signal) => protect(() => connection.prompt(content, signal)),
3389
+ /**
3390
+ * 取消当前轮次,超时则关闭连接。
3391
+ * @returns 取消完成的 HaiResult。
3392
+ * @example
3393
+ * ```ts
3394
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3395
+ * const result = await session.cancel()
3396
+ * ```
3397
+ */
3398
+ cancel: () => protect(() => connection.cancel())
3399
+ };
3400
+ }),
3401
+ /**
3402
+ * 读取对端原生会话列表;未声明相应能力时返回错误。
3403
+ * @param cursor - 上一次原生列表响应中的分页游标。
3404
+ * @returns 原生分页响应的 HaiResult。
3405
+ * @example
3406
+ * ```ts
3407
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3408
+ * const result = await connection.listSessions()
3409
+ * ```
3410
+ */
3411
+ listSessions: (cursor) => protect(() => connection.listSessions(cursor)),
3412
+ /**
3413
+ * 按原生 ID 删除远端会话;不会删除框架数据库会话记录。
3414
+ * @param id - 对端返回的原生会话 ID。
3415
+ * @returns 原生删除响应的 HaiResult;不支持时返回协议错误。
3416
+ * @example
3417
+ * ```ts
3418
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3419
+ * const result = await connection.deleteSession('native-id')
3420
+ * ```
3421
+ */
3422
+ deleteSession: (id) => protect(() => connection.deleteSession(id)),
3423
+ /**
3424
+ * 关闭连接和拥有的子进程;不删除远端文件或持久状态。
3425
+ * @returns 关闭完成的 HaiResult;可重复关闭。
3426
+ * @example
3427
+ * ```ts
3428
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3429
+ * const result = await connection.close()
3430
+ * ```
3431
+ */
3432
+ close: () => protect(async () => {
3433
+ await connection.close();
3434
+ connections.delete(connection);
3435
+ })
3436
+ };
3437
+ }),
3438
+ /**
3439
+ * 关闭子系统及拥有的资源;由 ai.close 生命周期调用。
3440
+ * @returns 所有已接受操作结束和拥有资源完成清理后的 Promise。
3441
+ * @throws 清理进程或容器失败时抛出 ACPFailure,由 ai.close 统一处理。
3442
+ * @example
3443
+ * ```ts
3444
+ * import { ai } from '@h-ai/ai'
3445
+ * // 前提:ai 已成功初始化。
3446
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3447
+ * await ai.close()
3448
+ * ```
3449
+ */
3450
+ async close() {
3451
+ closed = true;
3452
+ await Promise.allSettled(
3453
+ [...live.values()].map((value) => value.connection.cancel())
3454
+ );
3455
+ await Promise.allSettled(
3456
+ [...connections].map((connection) => connection.close())
3457
+ );
3458
+ await Promise.allSettled([...pending]);
3459
+ const results = await Promise.allSettled([...live.keys()].map(stop));
3460
+ const more = await Promise.allSettled(
3461
+ [...connections].map((connection) => connection.close())
3462
+ );
3463
+ connections.clear();
3464
+ if ([...results, ...more].some((result) => result.status === "rejected"))
3465
+ throw new ACPFailure("cleanup");
3466
+ }
3467
+ };
3468
+ async function createGitOperationsInput(id, runtime) {
3469
+ return createGitOperations(await getWorkspace(id), runtime).prepare();
3470
+ }
3471
+ }
705
3472
  var SENTENCE_BOUNDARY_REGEX = /[。!?;!?;\n]|\.(?=\s|$)/;
706
3473
  async function* streamSentences(stream) {
707
3474
  let buffer = "";
@@ -799,8 +3566,8 @@ async function describeHttpError(response) {
799
3566
  }
800
3567
  var logger3 = core.logger.child({ module: "ai", scope: "audio-ws" });
801
3568
  function openAudioWebSocket(url, headers, options) {
802
- return new Promise((resolve, reject) => {
803
- const ws = new WebSocket(url, { headers, handshakeTimeout: options?.timeout ?? 15e3 });
3569
+ return new Promise((resolve3, reject) => {
3570
+ const ws = new WebSocket2(url, { headers, handshakeTimeout: options?.timeout ?? 15e3 });
804
3571
  ws.binaryType = "arraybuffer";
805
3572
  const queue = [];
806
3573
  let waiting = null;
@@ -903,7 +3670,7 @@ function openAudioWebSocket(url, headers, options) {
903
3670
  }
904
3671
  }
905
3672
  };
906
- resolve(connection);
3673
+ resolve3(connection);
907
3674
  });
908
3675
  });
909
3676
  }
@@ -914,7 +3681,7 @@ var DOUBAO_CAPABILITIES = {
914
3681
  transcribe: { supported: true, realtimeAudioInput: true, speechBoundaryEvents: false, streamingTranscriptOutput: true },
915
3682
  synthesize: { supported: true, incrementalTextInput: true, streamingAudioOutput: true }
916
3683
  };
917
- var PROTOCOL_VERSION = 1;
3684
+ var PROTOCOL_VERSION2 = 1;
918
3685
  var HEADER_SIZE = 1;
919
3686
  var MSG_FULL_CLIENT = 1;
920
3687
  var MSG_AUDIO_ONLY_CLIENT = 2;
@@ -952,7 +3719,7 @@ var DOUBAO_FORMAT = {
952
3719
  };
953
3720
  function buildHeader(messageType, flags, serialization, compression) {
954
3721
  return Buffer.from([
955
- PROTOCOL_VERSION << 4 | HEADER_SIZE,
3722
+ PROTOCOL_VERSION2 << 4 | HEADER_SIZE,
956
3723
  messageType << 4 | flags,
957
3724
  serialization << 4 | compression,
958
3725
  0
@@ -1726,9 +4493,9 @@ function createOpenAIAudioProvider() {
1726
4493
  if (!model.apiKey)
1727
4494
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_audioMissingApiKey", { params: { provider: "openai" } }));
1728
4495
  try {
1729
- const client = createClient(model.apiKey, model.baseUrl, model.timeout);
4496
+ const client2 = createClient(model.apiKey, model.baseUrl, model.timeout);
1730
4497
  const file = await toUploadFile(audio);
1731
- const result = await client.audio.transcriptions.create({
4498
+ const result = await client2.audio.transcriptions.create({
1732
4499
  file,
1733
4500
  model: model.model,
1734
4501
  language,
@@ -1750,9 +4517,9 @@ function createOpenAIAudioProvider() {
1750
4517
  aiM("ai_audioUnsupportedInput", { params: { provider: "openai", reason: "streaming audio input" } })
1751
4518
  );
1752
4519
  }
1753
- const client = createClient(model.apiKey, model.baseUrl, model.timeout);
4520
+ const client2 = createClient(model.apiKey, model.baseUrl, model.timeout);
1754
4521
  const file = await toUploadFile(audio);
1755
- const stream = await client.audio.transcriptions.create({
4522
+ const stream = await client2.audio.transcriptions.create({
1756
4523
  file,
1757
4524
  model: model.model,
1758
4525
  language,
@@ -1775,8 +4542,8 @@ function createOpenAIAudioProvider() {
1775
4542
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_audioMissingApiKey", { params: { provider: "openai" } }));
1776
4543
  const out = resolveSynthesisOutput4({ format, sampleRate });
1777
4544
  try {
1778
- const client = createClient(model.apiKey, model.baseUrl, model.timeout);
1779
- const response = await client.audio.speech.create({
4545
+ const client2 = createClient(model.apiKey, model.baseUrl, model.timeout);
4546
+ const response = await client2.audio.speech.create({
1780
4547
  model: model.model,
1781
4548
  voice: voice ?? "alloy",
1782
4549
  input: text,
@@ -1795,12 +4562,12 @@ function createOpenAIAudioProvider() {
1795
4562
  if (!model.apiKey)
1796
4563
  throw audioError(HaiAIError.CONFIGURATION_ERROR, aiM("ai_audioMissingApiKey", { params: { provider: "openai" } }));
1797
4564
  const outFormat = resolveSynthesisOutput4({ format }).format;
1798
- const client = createClient(model.apiKey, model.baseUrl, model.timeout);
4565
+ const client2 = createClient(model.apiKey, model.baseUrl, model.timeout);
1799
4566
  const segments = typeof text === "string" ? [text] : streamSentences(text);
1800
4567
  for await (const segment of segments) {
1801
4568
  if (!segment)
1802
4569
  continue;
1803
- const response = await client.audio.speech.create({
4570
+ const response = await client2.audio.speech.create({
1804
4571
  model: model.model,
1805
4572
  voice: voice ?? "alloy",
1806
4573
  input: segment,
@@ -2854,12 +5621,12 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
2854
5621
  delete candidate.active;
2855
5622
  }
2856
5623
  async function waitForContext(signal) {
2857
- await new Promise((resolve, reject) => {
5624
+ await new Promise((resolve3, reject) => {
2858
5625
  const timer = setTimeout(finish, 50);
2859
5626
  function finish() {
2860
5627
  clearTimeout(timer);
2861
5628
  signal?.removeEventListener("abort", abort);
2862
- resolve();
5629
+ resolve3();
2863
5630
  }
2864
5631
  function abort() {
2865
5632
  clearTimeout(timer);
@@ -3579,7 +6346,7 @@ function createEmbeddingOperations(config) {
3579
6346
  const resolved = resolvedResult.data;
3580
6347
  const input = Array.isArray(request.input) ? request.input : [request.input];
3581
6348
  try {
3582
- const client = getOrCreateClient(resolved);
6349
+ const client2 = getOrCreateClient(resolved);
3583
6350
  const params = {
3584
6351
  model: resolved.model,
3585
6352
  input
@@ -3587,7 +6354,7 @@ function createEmbeddingOperations(config) {
3587
6354
  if (request.dimensions ?? embeddingConfig.dimensions) {
3588
6355
  params.dimensions = request.dimensions ?? embeddingConfig.dimensions;
3589
6356
  }
3590
- const response = await client.embeddings.create(params);
6357
+ const response = await client2.embeddings.create(params);
3591
6358
  return ok({
3592
6359
  model: response.model,
3593
6360
  data: response.data.map((item) => ({
@@ -5217,9 +7984,9 @@ ${contextText}
5217
7984
  async ingestFile(input) {
5218
7985
  try {
5219
7986
  const { readFile } = await import('fs/promises');
5220
- const { basename } = await import('path');
7987
+ const { basename: basename2 } = await import('path');
5221
7988
  const content = await readFile(input.filePath, { encoding: input.encoding ?? "utf-8" });
5222
- const fileName = basename(input.filePath);
7989
+ const fileName = basename2(input.filePath);
5223
7990
  const documentId = input.documentId ?? fileName;
5224
7991
  return this.ingest({
5225
7992
  documentId,
@@ -5294,13 +8061,13 @@ async function runWithCancellation(run, context) {
5294
8061
  const { signal } = context;
5295
8062
  if (signal.aborted)
5296
8063
  throw new DOMException("Aborted", "AbortError");
5297
- return await new Promise((resolve, reject) => {
8064
+ return await new Promise((resolve3, reject) => {
5298
8065
  const onAbort = () => reject(new DOMException("Aborted", "AbortError"));
5299
8066
  signal.addEventListener("abort", onAbort, { once: true });
5300
8067
  Promise.resolve().then(() => run(context)).then(
5301
8068
  (value) => {
5302
8069
  signal.removeEventListener("abort", onAbort);
5303
- resolve(value);
8070
+ resolve3(value);
5304
8071
  },
5305
8072
  (error) => {
5306
8073
  signal.removeEventListener("abort", onAbort);
@@ -5612,11 +8379,11 @@ function createAnthropicProvider(deps) {
5612
8379
  function resolveAnthropic(requestModel, tempModel) {
5613
8380
  const entry = requestModel ? config.llm.models?.find((m) => m.id === requestModel || m.model === requestModel) : void 0;
5614
8381
  const model = tempModel?.model ?? entry?.model ?? requestModel ?? config.llm.model ?? "claude-3-5-sonnet-latest";
5615
- const apiKey = tempModel?.apiKey ?? entry?.apiKey ?? config.llm.apiKey ?? (process.env.HAI_AI_LLM_APIKEY?.trim() || process.env.ANTHROPIC_API_KEY);
8382
+ const apiKey = tempModel?.apiKey ?? entry?.apiKey ?? config.llm.apiKey ?? (process2.env.HAI_AI_LLM_APIKEY?.trim() || process2.env.ANTHROPIC_API_KEY);
5616
8383
  if (!apiKey)
5617
8384
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_configError", { params: { error: "API Key is required for Anthropic" } }));
5618
- const globalBaseUrl = config.llm.protocol === "anthropic" ? config.llm.baseUrl ?? (process.env.HAI_AI_LLM_BASEURL?.trim() || void 0) : void 0;
5619
- const baseUrl = (tempModel?.baseUrl ?? entry?.baseUrl ?? globalBaseUrl ?? process.env.ANTHROPIC_BASE_URL ?? DEFAULT_ANTHROPIC_BASE_URL).replace(/\/+$/, "");
8385
+ const globalBaseUrl = config.llm.protocol === "anthropic" ? config.llm.baseUrl ?? (process2.env.HAI_AI_LLM_BASEURL?.trim() || void 0) : void 0;
8386
+ const baseUrl = (tempModel?.baseUrl ?? entry?.baseUrl ?? globalBaseUrl ?? process2.env.ANTHROPIC_BASE_URL ?? DEFAULT_ANTHROPIC_BASE_URL).replace(/\/+$/, "");
5620
8387
  return ok({
5621
8388
  apiKey,
5622
8389
  baseUrl,
@@ -5926,12 +8693,12 @@ function createOpenAIProvider(deps) {
5926
8693
  const tempCacheTtl = config.llm.tempModelCacheTtl ?? 6e5;
5927
8694
  function getClient(apiKey, baseURL, timeout) {
5928
8695
  const key = `${apiKey}::${baseURL}::${timeout}`;
5929
- let client = clientCache.get(key);
5930
- if (!client) {
5931
- client = new OpenAI3({ apiKey, baseURL, timeout });
5932
- clientCache.set(key, client);
8696
+ let client2 = clientCache.get(key);
8697
+ if (!client2) {
8698
+ client2 = new OpenAI3({ apiKey, baseURL, timeout });
8699
+ clientCache.set(key, client2);
5933
8700
  }
5934
- return client;
8701
+ return client2;
5935
8702
  }
5936
8703
  function getTempClient(apiKey, baseURL, timeout) {
5937
8704
  const now = Date.now();
@@ -5943,9 +8710,9 @@ function createOpenAIProvider(deps) {
5943
8710
  const cached = tempClientCache.get(key);
5944
8711
  if (cached)
5945
8712
  return cached.client;
5946
- const client = new OpenAI3({ apiKey, baseURL, timeout });
5947
- tempClientCache.set(key, { client, expiresAt: now + tempCacheTtl });
5948
- return client;
8713
+ const client2 = new OpenAI3({ apiKey, baseURL, timeout });
8714
+ tempClientCache.set(key, { client: client2, expiresAt: now + tempCacheTtl });
8715
+ return client2;
5949
8716
  }
5950
8717
  function resolveClient(requestModel, tempModel) {
5951
8718
  if (tempModel)
@@ -5960,10 +8727,10 @@ function createOpenAIProvider(deps) {
5960
8727
  return ok({ client: getClient(apiKey ?? "", baseUrl ?? "https://api.openai.com/v1", timeout), model, protocol: resolvedApi });
5961
8728
  }
5962
8729
  function resolveTempClient(tempModel) {
5963
- const apiKey = tempModel.apiKey ?? config.llm.apiKey ?? (process.env.HAI_AI_LLM_APIKEY?.trim() || process.env.OPENAI_API_KEY);
8730
+ const apiKey = tempModel.apiKey ?? config.llm.apiKey ?? (process2.env.HAI_AI_LLM_APIKEY?.trim() || process2.env.OPENAI_API_KEY);
5964
8731
  if (!apiKey)
5965
8732
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_configError", { params: { error: "API Key is required for temp model" } }));
5966
- const baseUrl = tempModel.baseUrl ?? config.llm.baseUrl ?? (process.env.HAI_AI_LLM_BASEURL?.trim() || process.env.OPENAI_BASE_URL) ?? "https://api.openai.com/v1";
8733
+ const baseUrl = tempModel.baseUrl ?? config.llm.baseUrl ?? (process2.env.HAI_AI_LLM_BASEURL?.trim() || process2.env.OPENAI_BASE_URL) ?? "https://api.openai.com/v1";
5967
8734
  const timeout = tempModel.timeout ?? config.llm.timeout ?? 6e4;
5968
8735
  const api = (tempModel.protocol ?? config.llm.protocol) === "responses" ? "responses" : "chat";
5969
8736
  return ok({
@@ -5995,11 +8762,11 @@ function createOpenAIProvider(deps) {
5995
8762
  if (!clientResult.success)
5996
8763
  return clientResult;
5997
8764
  const resolved = clientResult.data;
5998
- const { client, model, maxTokens, temperature, protocol: api } = resolved;
8765
+ const { client: client2, model, maxTokens, temperature, protocol: api } = resolved;
5999
8766
  const { objectId: _objectId, sessionId: _sessionId, tempModel: _tempModel, signal, ...openaiRequest } = request;
6000
8767
  try {
6001
8768
  if (api === "responses") {
6002
- const response2 = await client.responses.create(buildResponsesParams(request, resolved), { signal });
8769
+ const response2 = await client2.responses.create(buildResponsesParams(request, resolved), { signal });
6003
8770
  return ok(responsesToChatResponse(response2, model));
6004
8771
  }
6005
8772
  const openaiMessages = request.messages.map(toOpenAIMessage);
@@ -6011,7 +8778,7 @@ function createOpenAIProvider(deps) {
6011
8778
  ...temperature !== void 0 && openaiRequest.temperature == null ? { temperature } : {},
6012
8779
  stream: false
6013
8780
  };
6014
- const response = signal ? await client.chat.completions.create(params, { signal }) : await client.chat.completions.create(params);
8781
+ const response = signal ? await client2.chat.completions.create(params, { signal }) : await client2.chat.completions.create(params);
6015
8782
  return ok(response);
6016
8783
  } catch (error) {
6017
8784
  const mapped = toAIError(error);
@@ -6023,10 +8790,10 @@ function createOpenAIProvider(deps) {
6023
8790
  if (!clientResult.success)
6024
8791
  throw new Error(clientResult.error.message);
6025
8792
  const resolved = clientResult.data;
6026
- const { client, model, maxTokens, temperature, protocol: api } = resolved;
8793
+ const { client: client2, model, maxTokens, temperature, protocol: api } = resolved;
6027
8794
  const { objectId: _objectId, sessionId: _sessionId, tempModel: _tempModel, signal, ...openaiRequest } = request;
6028
8795
  if (api === "responses") {
6029
- const responsesStream = await client.responses.create(
8796
+ const responsesStream = await client2.responses.create(
6030
8797
  { ...buildResponsesParams(request, resolved), stream: true },
6031
8798
  { signal }
6032
8799
  );
@@ -6046,7 +8813,7 @@ function createOpenAIProvider(deps) {
6046
8813
  ...temperature !== void 0 && openaiRequest.temperature == null ? { temperature } : {},
6047
8814
  stream: true
6048
8815
  };
6049
- const stream = signal ? await client.chat.completions.create(params, { signal }) : await client.chat.completions.create(params);
8816
+ const stream = signal ? await client2.chat.completions.create(params, { signal }) : await client2.chat.completions.create(params);
6050
8817
  for await (const chunk of stream) {
6051
8818
  yield chunk;
6052
8819
  }
@@ -6055,9 +8822,9 @@ function createOpenAIProvider(deps) {
6055
8822
  const clientResult = resolveClient();
6056
8823
  if (!clientResult.success)
6057
8824
  return clientResult;
6058
- const { client } = clientResult.data;
8825
+ const { client: client2 } = clientResult.data;
6059
8826
  try {
6060
- const response = await client.models.list();
8827
+ const response = await client2.models.list();
6061
8828
  const models = response.data.map((m) => m.id);
6062
8829
  return ok(models);
6063
8830
  } catch (error) {
@@ -6093,7 +8860,19 @@ function createLLMProvider(deps) {
6093
8860
  // src/llm/ai-llm-functions.ts
6094
8861
  var logger18 = core.logger.child({ module: "ai", scope: "llm" });
6095
8862
  function createAILLMFunctions(config, deps) {
6096
- const provider = createLLMProvider({ config });
8863
+ let provider = createLLMProvider({ config });
8864
+ let signature = JSON.stringify(config.llm);
8865
+ async function currentProvider() {
8866
+ if (!deps?.resolveConfig)
8867
+ return provider;
8868
+ const snapshot2 = await deps.resolveConfig();
8869
+ const next = JSON.stringify(snapshot2.llm);
8870
+ if (next !== signature) {
8871
+ provider = createLLMProvider({ config: snapshot2 });
8872
+ signature = next;
8873
+ }
8874
+ return provider;
8875
+ }
6097
8876
  const recordStore = deps?.recordStore;
6098
8877
  const sessionStore = deps?.sessionStore;
6099
8878
  let recordSeq = 0;
@@ -6108,13 +8887,26 @@ function createAILLMFunctions(config, deps) {
6108
8887
  return "New Session";
6109
8888
  }
6110
8889
  async function saveRecordAndSession(record, messages) {
6111
- await recordStore.save(record.id, record, { objectId: record.objectId, sessionId: record.sessionId });
6112
- logger18.trace("Chat record saved", { id: record.id, objectId: record.objectId, sessionId: record.sessionId });
8890
+ await recordStore.save(record.id, record, {
8891
+ objectId: record.objectId,
8892
+ sessionId: record.sessionId
8893
+ });
8894
+ logger18.trace("Chat record saved", {
8895
+ id: record.id,
8896
+ objectId: record.objectId,
8897
+ sessionId: record.sessionId
8898
+ });
6113
8899
  if (sessionStore) {
6114
8900
  await updateSessionInfo(sessionStore, record, (existing, now) => {
6115
8901
  if (existing?.deleted)
6116
8902
  return void 0;
6117
- return existing ? { ...existing, updatedAt: now } : { sessionId: record.sessionId, objectId: record.objectId, title: extractTitle(messages), createdAt: now, updatedAt: now };
8903
+ return existing ? { ...existing, updatedAt: now } : {
8904
+ sessionId: record.sessionId,
8905
+ objectId: record.objectId,
8906
+ title: extractTitle(messages),
8907
+ createdAt: now,
8908
+ updatedAt: now
8909
+ };
6118
8910
  });
6119
8911
  }
6120
8912
  }
@@ -6122,9 +8914,20 @@ function createAILLMFunctions(config, deps) {
6122
8914
  if (request.tempModel && Object.hasOwn(request.tempModel, "api"))
6123
8915
  return err(HaiAIError.INVALID_REQUEST, aiM("ai_llmProtocolRenamed"));
6124
8916
  const start = Date.now();
6125
- const context = { model: request.model ?? request.tempModel?.model, messageCount: request.messages.length };
8917
+ const context = {
8918
+ model: request.model ?? request.tempModel?.model,
8919
+ messageCount: request.messages.length
8920
+ };
6126
8921
  logger18.debug("LLM chat started", context);
6127
- const result = await provider.chat(request);
8922
+ let result;
8923
+ try {
8924
+ result = await (await currentProvider()).chat(request);
8925
+ } catch {
8926
+ return err(
8927
+ HaiAIError.RESOURCE_OPERATION_FAILED,
8928
+ aiM("ai_resourceOperationFailed")
8929
+ );
8930
+ }
6128
8931
  if (result.success) {
6129
8932
  logger18.info("LLM chat completed", {
6130
8933
  ...context,
@@ -6157,7 +8960,11 @@ function createAILLMFunctions(config, deps) {
6157
8960
  content: choice?.message?.content ?? "",
6158
8961
  toolCalls: choice?.message?.tool_calls,
6159
8962
  finishReason: choice?.finish_reason ?? "stop",
6160
- usage: response.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
8963
+ usage: response.usage ?? {
8964
+ prompt_tokens: 0,
8965
+ completion_tokens: 0,
8966
+ total_tokens: 0
8967
+ }
6161
8968
  },
6162
8969
  createdAt: Date.now(),
6163
8970
  duration: Date.now() - start
@@ -6173,7 +8980,10 @@ function createAILLMFunctions(config, deps) {
6173
8980
  if (request.tempModel && Object.hasOwn(request.tempModel, "api"))
6174
8981
  throw new Error(aiM("ai_llmProtocolRenamed"));
6175
8982
  const start = Date.now();
6176
- const logContext = { model: request.model ?? request.tempModel?.model, messageCount: request.messages.length };
8983
+ const logContext = {
8984
+ model: request.model ?? request.tempModel?.model,
8985
+ messageCount: request.messages.length
8986
+ };
6177
8987
  logger18.debug("LLM chat stream started", logContext);
6178
8988
  const shouldRecord = !!(request.objectId && recordStore && request.enablePersist !== false);
6179
8989
  let content = "";
@@ -6182,7 +8992,7 @@ function createAILLMFunctions(config, deps) {
6182
8992
  let streamId = "";
6183
8993
  let resolvedModel = request.model ?? request.tempModel?.model ?? "";
6184
8994
  try {
6185
- for await (const chunk of provider.chatStream(request)) {
8995
+ for await (const chunk of (await currentProvider()).chatStream(request)) {
6186
8996
  yield chunk;
6187
8997
  if (!shouldRecord)
6188
8998
  continue;
@@ -6198,7 +9008,11 @@ function createAILLMFunctions(config, deps) {
6198
9008
  if (choice.delta.tool_calls) {
6199
9009
  for (const tc of choice.delta.tool_calls) {
6200
9010
  if (!toolCalls[tc.index]) {
6201
- toolCalls[tc.index] = { id: tc.id ?? "", type: "function", function: { name: tc.function?.name ?? "", arguments: "" } };
9011
+ toolCalls[tc.index] = {
9012
+ id: tc.id ?? "",
9013
+ type: "function",
9014
+ function: { name: tc.function?.name ?? "", arguments: "" }
9015
+ };
6202
9016
  }
6203
9017
  if (tc.function?.arguments)
6204
9018
  toolCalls[tc.index].function.arguments += tc.function.arguments;
@@ -6218,12 +9032,20 @@ function createAILLMFunctions(config, deps) {
6218
9032
  finishReason
6219
9033
  });
6220
9034
  } catch (error) {
6221
- logger18.warn("LLM chat stream failed", { ...logContext, durationMs: Date.now() - start, error });
9035
+ logger18.warn("LLM chat stream failed", {
9036
+ ...logContext,
9037
+ durationMs: Date.now() - start,
9038
+ error
9039
+ });
6222
9040
  throw error;
6223
9041
  }
6224
9042
  if (shouldRecord) {
6225
9043
  try {
6226
- const usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
9044
+ const usage = {
9045
+ prompt_tokens: 0,
9046
+ completion_tokens: 0,
9047
+ total_tokens: 0
9048
+ };
6227
9049
  const record = {
6228
9050
  id: `${streamId || "stream"}_${Date.now()}_${recordSeq++}`,
6229
9051
  objectId: request.objectId,
@@ -6260,7 +9082,15 @@ function createAILLMFunctions(config, deps) {
6260
9082
  });
6261
9083
  return ok(records);
6262
9084
  } catch (error) {
6263
- return err(HaiAIError.LLM_HISTORY_FAILED, aiM("ai_llmHistoryFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
9085
+ return err(
9086
+ HaiAIError.LLM_HISTORY_FAILED,
9087
+ aiM("ai_llmHistoryFailed", {
9088
+ params: {
9089
+ error: error instanceof Error ? error.message : String(error)
9090
+ }
9091
+ }),
9092
+ error
9093
+ );
6264
9094
  }
6265
9095
  }
6266
9096
  async function listSessions(objectId) {
@@ -6274,13 +9104,30 @@ function createAILLMFunctions(config, deps) {
6274
9104
  });
6275
9105
  return ok(sessions.filter((item) => !item.deleted));
6276
9106
  } catch (error) {
6277
- return err(HaiAIError.LLM_HISTORY_FAILED, aiM("ai_llmHistoryFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
9107
+ return err(
9108
+ HaiAIError.LLM_HISTORY_FAILED,
9109
+ aiM("ai_llmHistoryFailed", {
9110
+ params: {
9111
+ error: error instanceof Error ? error.message : String(error)
9112
+ }
9113
+ }),
9114
+ error
9115
+ );
6278
9116
  }
6279
9117
  }
6280
9118
  const llm = {
6281
9119
  chat: chatWithRecord,
6282
9120
  chatStream: chatStreamWithRecord,
6283
- listModels: () => provider.listModels(),
9121
+ listModels: async () => {
9122
+ try {
9123
+ return await (await currentProvider()).listModels();
9124
+ } catch {
9125
+ return err(
9126
+ HaiAIError.RESOURCE_OPERATION_FAILED,
9127
+ aiM("ai_resourceOperationFailed")
9128
+ );
9129
+ }
9130
+ },
6284
9131
  getHistory,
6285
9132
  listSessions,
6286
9133
  async ask(question, options) {
@@ -6334,7 +9181,15 @@ function createAILLMFunctions(config, deps) {
6334
9181
  const maxRepairs = request.maxRepairs ?? 1;
6335
9182
  let lastError = "";
6336
9183
  for (let attempt = 0; attempt <= maxRepairs; attempt++) {
6337
- const messages = attempt === 0 ? baseMessages : [...baseMessages, { role: "user", content: aiM("ai_generateObjectRepair", { params: { error: lastError } }) }];
9184
+ const messages = attempt === 0 ? baseMessages : [
9185
+ ...baseMessages,
9186
+ {
9187
+ role: "user",
9188
+ content: aiM("ai_generateObjectRepair", {
9189
+ params: { error: lastError }
9190
+ })
9191
+ }
9192
+ ];
6338
9193
  const result = await chatWithRecord({
6339
9194
  messages,
6340
9195
  model: request.model,
@@ -6342,7 +9197,10 @@ function createAILLMFunctions(config, deps) {
6342
9197
  tempModel: request.tempModel,
6343
9198
  enablePersist: false,
6344
9199
  signal: request.signal,
6345
- response_format: { type: "json_schema", json_schema: { name: schemaName, schema: jsonSchema, strict: true } }
9200
+ response_format: {
9201
+ type: "json_schema",
9202
+ json_schema: { name: schemaName, schema: jsonSchema, strict: true }
9203
+ }
6346
9204
  });
6347
9205
  if (!result.success)
6348
9206
  return result;
@@ -6360,7 +9218,12 @@ function createAILLMFunctions(config, deps) {
6360
9218
  return ok(validated.data);
6361
9219
  lastError = validated.error.message;
6362
9220
  }
6363
- return err(HaiAIError.INVALID_REQUEST, aiM("ai_generateObjectFailed", { params: { error: lastError || "unknown" } }));
9221
+ return err(
9222
+ HaiAIError.INVALID_REQUEST,
9223
+ aiM("ai_generateObjectFailed", {
9224
+ params: { error: lastError || "unknown" }
9225
+ })
9226
+ );
6364
9227
  }
6365
9228
  };
6366
9229
  const tools = {
@@ -6498,25 +9361,25 @@ function createAIMCPFunctions() {
6498
9361
  }
6499
9362
  };
6500
9363
  }
6501
- function createScopedMemory(core33, binding) {
9364
+ function createScopedMemory(core32, binding) {
6502
9365
  const { objectId, scope } = binding;
6503
9366
  const accessScope = { objectId, scope };
6504
9367
  return {
6505
- extract: (messages, options) => core33.extract(messages, { ...options, objectId, scope }),
6506
- recall: (query, options) => core33.recall(query, { ...options, objectId, scope }),
6507
- injectMemories: (messages, options) => core33.injectMemories(messages, { ...options, objectId, scope }),
6508
- add: (entry) => core33.add({ ...entry, objectId, scope }),
6509
- update: (memoryId, updates) => core33.update(memoryId, updates, accessScope),
6510
- get: (memoryId) => core33.get(memoryId, accessScope),
6511
- remove: (memoryId) => core33.remove(memoryId, accessScope),
6512
- list: (options) => core33.list({ ...options, objectId, scope }),
6513
- listPage: (options) => core33.listPage({ ...options, objectId, scope }),
6514
- clear: (options) => core33.clear({ ...options, objectId, scope })
9368
+ extract: (messages, options) => core32.extract(messages, { ...options, objectId, scope }),
9369
+ recall: (query, options) => core32.recall(query, { ...options, objectId, scope }),
9370
+ injectMemories: (messages, options) => core32.injectMemories(messages, { ...options, objectId, scope }),
9371
+ add: (entry) => core32.add({ ...entry, objectId, scope }),
9372
+ update: (memoryId, updates) => core32.update(memoryId, updates, accessScope),
9373
+ get: (memoryId) => core32.get(memoryId, accessScope),
9374
+ remove: (memoryId) => core32.remove(memoryId, accessScope),
9375
+ list: (options) => core32.list({ ...options, objectId, scope }),
9376
+ listPage: (options) => core32.listPage({ ...options, objectId, scope }),
9377
+ clear: (options) => core32.clear({ ...options, objectId, scope })
6515
9378
  };
6516
9379
  }
6517
- function createMemoryOperations(core33) {
9380
+ function createMemoryOperations(core32) {
6518
9381
  return {
6519
- ...core33,
9382
+ ...core32,
6520
9383
  /**
6521
9384
  * 清空记忆(空过滤保护)。
6522
9385
  *
@@ -6528,15 +9391,15 @@ function createMemoryOperations(core33) {
6528
9391
  if (!hasFilter) {
6529
9392
  return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryClearRequiresFilter"));
6530
9393
  }
6531
- return core33.clear(options);
9394
+ return core32.clear(options);
6532
9395
  },
6533
- scoped: (binding) => createScopedMemory(core33, binding),
9396
+ scoped: (binding) => createScopedMemory(core32, binding),
6534
9397
  admin: {
6535
9398
  async clearAll(options) {
6536
9399
  if (options?.confirm !== true) {
6537
9400
  return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryClearAllUnconfirmed"));
6538
9401
  }
6539
- return core33.clear();
9402
+ return core32.clear();
6540
9403
  }
6541
9404
  }
6542
9405
  };
@@ -6887,7 +9750,7 @@ function buildMem0Config(deps) {
6887
9750
  const vectorResolved = buildVectorStoreConfig(deps);
6888
9751
  if (!vectorResolved.success)
6889
9752
  return vectorResolved;
6890
- if (core.module.requiresSharedState(process.env) && !vectorResolved.data.persistent)
9753
+ if (core.module.requiresSharedState(process2.env) && !vectorResolved.data.persistent)
6891
9754
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_memorySharedBackendRequired"));
6892
9755
  return ok({
6893
9756
  vectorProvider: vectorResolved.data.provider,
@@ -7099,16 +9962,16 @@ async function createMem0OssMemoryOperations(deps) {
7099
9962
  const sdk = await import('@qdrant/js-client-rest').catch(() => void 0);
7100
9963
  if (sdk && typeof sdk.QdrantClient?.prototype?.search === "function") {
7101
9964
  const url = deps.vectorBackend?.url ?? "";
7102
- const client = new sdk.QdrantClient({
9965
+ const client2 = new sdk.QdrantClient({
7103
9966
  url,
7104
9967
  port: Number(new URL(url).port) || 6333,
7105
9968
  apiKey: deps.vectorBackend?.apiKey
7106
9969
  });
7107
- const upsert = client.upsert.bind(client);
7108
- const remove = client.delete.bind(client);
7109
- client.upsert = (collection, options) => upsert(collection, { ...options, wait: true });
7110
- client.delete = (collection, options) => remove(collection, { ...options, wait: true });
7111
- configResult.data.config.vectorStore.config.client = client;
9970
+ const upsert = client2.upsert.bind(client2);
9971
+ const remove = client2.delete.bind(client2);
9972
+ client2.upsert = (collection, options) => upsert(collection, { ...options, wait: true });
9973
+ client2.delete = (collection, options) => remove(collection, { ...options, wait: true });
9974
+ configResult.data.config.vectorStore.config.client = client2;
7112
9975
  } else {
7113
9976
  const failure = err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_memoryBackendSdkIncompatible"));
7114
9977
  if (!failure.success)
@@ -8401,7 +11264,8 @@ function createRerankOperations(config) {
8401
11264
  "Content-Type": "application/json",
8402
11265
  "Authorization": `Bearer ${resolved.apiKey}`
8403
11266
  },
8404
- body: JSON.stringify(body)
11267
+ body: JSON.stringify(body),
11268
+ signal: AbortSignal.timeout(resolved.timeout)
8405
11269
  });
8406
11270
  if (!response.ok) {
8407
11271
  const errorText = await response.text().catch(() => response.statusText);
@@ -8451,7 +11315,7 @@ function createRerankOperations(config) {
8451
11315
  };
8452
11316
  }
8453
11317
  var logger28 = core.logger.child({ module: "ai", scope: "retrieval" });
8454
- function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rerankOps) {
11318
+ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rerankOps, registry) {
8455
11319
  const vectorStoreCache = /* @__PURE__ */ new Map();
8456
11320
  function getVectorStore(collection) {
8457
11321
  let store = vectorStoreCache.get(collection);
@@ -8472,6 +11336,10 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
8472
11336
  * @returns `ok(undefined)` 注册成功;`err` 含错误码 `RETRIEVAL_FAILED`(重复注册)
8473
11337
  */
8474
11338
  async addSource(source) {
11339
+ if (registry) {
11340
+ const result = await registry.operations.register(source);
11341
+ return result.success ? ok(void 0) : result;
11342
+ }
8475
11343
  const existing = await sourceStore.get(source.id);
8476
11344
  if (existing) {
8477
11345
  return err(HaiAIError.RETRIEVAL_FAILED, aiM("ai_internalError", { params: { error: `Source '${source.id}' already exists` } }));
@@ -8489,6 +11357,8 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
8489
11357
  * @returns `ok(undefined)` 删除成功;`err` 含错误码 `RETRIEVAL_SOURCE_NOT_FOUND`
8490
11358
  */
8491
11359
  async removeSource(sourceId) {
11360
+ if (registry)
11361
+ return registry.operations.remove(sourceId);
8492
11362
  const existing = await sourceStore.get(sourceId);
8493
11363
  if (!existing) {
8494
11364
  return err(HaiAIError.RETRIEVAL_SOURCE_NOT_FOUND, aiM("ai_internalError", { params: { error: `Source '${sourceId}' not found` } }));
@@ -8503,7 +11373,7 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
8503
11373
  * @returns 全部检索源数组(无源时返回空数组)
8504
11374
  */
8505
11375
  async listSources() {
8506
- return sourceStore.query({});
11376
+ return registry ? registry.values() : sourceStore.query({});
8507
11377
  },
8508
11378
  /**
8509
11379
  * 执行向量检索
@@ -8518,13 +11388,13 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
8518
11388
  */
8519
11389
  async retrieve(request) {
8520
11390
  const startTime = Date.now();
8521
- const targetSources = await sourceStore.query(
8522
- request.sources?.length ? { where: { id: { $in: request.sources } } } : {}
8523
- );
8524
- if (targetSources.length === 0) {
8525
- return err(HaiAIError.RETRIEVAL_SOURCE_NOT_FOUND, aiM("ai_internalError", { params: { error: "No retrieval sources configured" } }));
8526
- }
8527
11391
  try {
11392
+ const targetSources = registry ? (await registry.values()).filter((source) => !request.sources?.length || request.sources.includes(source.id)) : await sourceStore.query(
11393
+ request.sources?.length ? { where: { id: { $in: request.sources } } } : {}
11394
+ );
11395
+ if (targetSources.length === 0) {
11396
+ return err(HaiAIError.RETRIEVAL_SOURCE_NOT_FOUND, aiM("ai_internalError", { params: { error: "No retrieval sources configured" } }));
11397
+ }
8528
11398
  const embedResult = await embeddingOps.embedText(request.query);
8529
11399
  if (!embedResult.success) {
8530
11400
  return err(HaiAIError.RETRIEVAL_FAILED, aiM("ai_internalError", { params: { error: "Failed to embed query" } }), embedResult.error);
@@ -8703,38 +11573,207 @@ function createTokenOperations(config) {
8703
11573
  }
8704
11574
 
8705
11575
  // src/ai-functions.ts
8706
- var logger30 = core.logger.child({ module: "ai", scope: "functions" });
8707
11576
  async function createAISubsystems(config, deps) {
8708
11577
  const { storeProvider, datapipe: datapipeDep } = deps;
8709
11578
  const memoryParsed = MemoryConfigSchema.parse(config.memory ?? {});
8710
11579
  const useMem0Oss = memoryParsed.provider === "mem0";
8711
- const chatRecordStore = storeProvider.createRelStore("hai_ai_chat_records", { hasObjectId: true, hasSessionId: true });
8712
- const sessionStore = storeProvider.createRelStore("hai_ai_sessions", { hasObjectId: true });
8713
- const sourceStore = storeProvider.createRelStore("hai_ai_retrieval_sources");
8714
- const memoryStore = useMem0Oss ? null : storeProvider.createRelStore("hai_ai_memory", { hasObjectId: true, hasScopeIndex: true });
8715
- const contextStore = storeProvider.createRelStore("hai_ai_context", { hasObjectId: true, hasSessionId: true });
8716
- const personaStore = storeProvider.createRelStore("hai_ai_persona", { hasObjectId: true });
11580
+ const chatRecordStore = storeProvider.createRelStore(
11581
+ "hai_ai_chat_records",
11582
+ { hasObjectId: true, hasSessionId: true }
11583
+ );
11584
+ const sessionStore = storeProvider.createRelStore(
11585
+ "hai_ai_sessions",
11586
+ { hasObjectId: true }
11587
+ );
11588
+ const sourceStore = storeProvider.createRelStore(
11589
+ "hai_ai_retrieval_sources"
11590
+ );
11591
+ const memoryStore = useMem0Oss ? null : storeProvider.createRelStore("hai_ai_memory", {
11592
+ hasObjectId: true,
11593
+ hasScopeIndex: true
11594
+ });
11595
+ const contextStore = storeProvider.createRelStore(
11596
+ "hai_ai_context",
11597
+ { hasObjectId: true, hasSessionId: true }
11598
+ );
11599
+ const personaStore = storeProvider.createRelStore(
11600
+ "hai_ai_persona",
11601
+ { hasObjectId: true }
11602
+ );
8717
11603
  const a2aStores = config.a2a ? createA2AStores(storeProvider) : null;
8718
11604
  const memoryVectorStore = useMem0Oss ? null : storeProvider.createVectorStore("hai_ai_memory_vectors");
11605
+ let registriesOpen = true;
11606
+ const isOpen = () => registriesOpen;
11607
+ const llmModels = createResourceRegistry(
11608
+ storeProvider.createRelStore("hai_ai_llm_models"),
11609
+ ModelEntrySchema,
11610
+ config.llm.models ?? [],
11611
+ isOpen
11612
+ );
11613
+ const audioModels = createResourceRegistry(
11614
+ storeProvider.createRelStore("hai_ai_audio_models"),
11615
+ AudioModelEntrySchema,
11616
+ config.audio?.models ?? [],
11617
+ isOpen
11618
+ );
11619
+ const imageModels = createResourceRegistry(
11620
+ storeProvider.createRelStore("hai_ai_image_models"),
11621
+ ImageModelEntrySchema,
11622
+ config.image?.models ?? [],
11623
+ isOpen
11624
+ );
11625
+ const mcpServers = createResourceRegistry(
11626
+ storeProvider.createRelStore("hai_ai_mcp_servers"),
11627
+ MCPExternalServerSchema,
11628
+ config.mcp?.servers ?? [],
11629
+ isOpen
11630
+ );
11631
+ const retrievalSources = createResourceRegistry(
11632
+ sourceStore,
11633
+ RetrievalSourceSchema,
11634
+ config.retrieval?.sources ?? [],
11635
+ isOpen
11636
+ );
11637
+ async function llmConfig() {
11638
+ return {
11639
+ ...config,
11640
+ llm: { ...config.llm, models: await llmModels.values() }
11641
+ };
11642
+ }
11643
+ async function audioConfig() {
11644
+ return {
11645
+ ...config,
11646
+ audio: AudioConfigSchema.parse({
11647
+ ...config.audio,
11648
+ models: await audioModels.values()
11649
+ })
11650
+ };
11651
+ }
11652
+ async function imageConfig() {
11653
+ return {
11654
+ ...config,
11655
+ image: ImageConfigSchema.parse({
11656
+ ...config.image,
11657
+ models: await imageModels.values()
11658
+ })
11659
+ };
11660
+ }
11661
+ async function mcpLookup(ids) {
11662
+ const servers = [];
11663
+ for (const id of ids) {
11664
+ const entry = await mcpServers.read(id);
11665
+ if (!entry)
11666
+ throw new Error("MCP resource missing");
11667
+ const server = entry.value;
11668
+ if (server.transport === "stdio") {
11669
+ servers.push({
11670
+ name: server.name,
11671
+ command: server.command,
11672
+ args: server.args,
11673
+ env: Object.entries(resolveEnvironment(server.environmentRefs)).map(
11674
+ ([name, value]) => ({ name, value })
11675
+ )
11676
+ });
11677
+ } else {
11678
+ servers.push({
11679
+ type: server.transport,
11680
+ name: server.name,
11681
+ url: server.url,
11682
+ headers: Object.entries(resolveEnvironment(server.headerEnv)).map(
11683
+ ([name, value]) => ({ name, value })
11684
+ )
11685
+ });
11686
+ }
11687
+ }
11688
+ return servers;
11689
+ }
11690
+ const acp = createACPOperations(
11691
+ storeProvider,
11692
+ config,
11693
+ deps.acpAdapters,
11694
+ llmConfig,
11695
+ mcpLookup
11696
+ );
8719
11697
  await storeProvider.initialize();
8720
- const llmFunctions = createAILLMFunctions(config, { recordStore: chatRecordStore, sessionStore });
8721
- const llm = llmFunctions.llm;
8722
- const mcp = createAIMCPFunctions();
8723
- const embedding = createEmbeddingOperations(config);
8724
- const reasoning = createReasoningOperations(config, llm);
8725
- const rerank = createRerankOperations(config);
8726
- const retrieval = createRetrievalOperations(embedding, storeProvider, sourceStore, rerank);
8727
- if (config.retrieval?.sources?.length) {
8728
- const configSources = RetrievalConfigSchema.parse(config.retrieval).sources ?? [];
8729
- sourceStore.saveMany(configSources.map((s) => ({ id: s.id, data: s }))).catch((e) => logger30.warn("Failed to pre-register retrieval sources", { error: e }));
11698
+ const llmFunctions = createAILLMFunctions(config, {
11699
+ recordStore: chatRecordStore,
11700
+ sessionStore,
11701
+ resolveConfig: llmConfig
11702
+ });
11703
+ const llm = Object.assign(llmFunctions.llm, {
11704
+ models: llmModels.operations
11705
+ });
11706
+ const mcp = Object.assign(createAIMCPFunctions(), {
11707
+ servers: mcpServers.operations
11708
+ });
11709
+ async function resourceCall(operation) {
11710
+ try {
11711
+ return await operation();
11712
+ } catch {
11713
+ return err(
11714
+ HaiAIError.RESOURCE_OPERATION_FAILED,
11715
+ aiM("ai_resourceOperationFailed")
11716
+ );
11717
+ }
8730
11718
  }
11719
+ const embedding = {
11720
+ embed: (request) => resourceCall(
11721
+ async () => createEmbeddingOperations(await llmConfig()).embed(request)
11722
+ ),
11723
+ embedText: (text) => resourceCall(
11724
+ async () => createEmbeddingOperations(await llmConfig()).embedText(text)
11725
+ ),
11726
+ embedBatch: (texts) => resourceCall(
11727
+ async () => createEmbeddingOperations(await llmConfig()).embedBatch(texts)
11728
+ )
11729
+ };
11730
+ const reasoning = {
11731
+ run: (query, options) => resourceCall(async () => createReasoningOperations(await llmConfig(), llm).run(query, options)),
11732
+ async *runStream(query, options) {
11733
+ yield* createReasoningOperations(await llmConfig(), llm).runStream(query, options);
11734
+ }
11735
+ };
11736
+ const rerank = {
11737
+ rerank: (request) => resourceCall(
11738
+ async () => createRerankOperations(await llmConfig()).rerank(request)
11739
+ ),
11740
+ rerankTexts: (query, texts, topN) => resourceCall(
11741
+ async () => createRerankOperations(await llmConfig()).rerankTexts(
11742
+ query,
11743
+ texts,
11744
+ topN
11745
+ )
11746
+ )
11747
+ };
11748
+ const retrieval = Object.assign(
11749
+ createRetrievalOperations(
11750
+ embedding,
11751
+ storeProvider,
11752
+ sourceStore,
11753
+ rerank,
11754
+ retrievalSources
11755
+ ),
11756
+ { sources: retrievalSources.operations }
11757
+ );
8731
11758
  const rag = createRagOperations(llm, retrieval);
8732
11759
  const knowledgeParsed = KnowledgeConfigSchema.parse(config.knowledge ?? {});
8733
11760
  const knowledgeStore = storeProvider.createKnowledgeStore?.();
8734
- const knowledge = createKnowledgeOperations(knowledgeParsed, llm, embedding, datapipeDep, knowledgeStore);
8735
- const memoryCore = memoryStore && memoryVectorStore ? createNativeMemoryOperations(memoryParsed, llm, embedding, memoryStore, memoryVectorStore) : await createMem0OssMemoryOperations({
11761
+ const knowledge = createKnowledgeOperations(
11762
+ knowledgeParsed,
11763
+ llm,
11764
+ embedding,
11765
+ datapipeDep,
11766
+ knowledgeStore
11767
+ );
11768
+ const memoryCore = memoryStore && memoryVectorStore ? createNativeMemoryOperations(
11769
+ memoryParsed,
11770
+ llm,
11771
+ embedding,
11772
+ memoryStore,
11773
+ memoryVectorStore
11774
+ ) : await createMem0OssMemoryOperations({
8736
11775
  config: memoryParsed,
8737
- aiConfig: config,
11776
+ aiConfig: await llmConfig(),
8738
11777
  llm,
8739
11778
  collectionName: "hai_ai_memory",
8740
11779
  embeddingDims: config.embedding?.dimensions,
@@ -8745,20 +11784,109 @@ async function createAISubsystems(config, deps) {
8745
11784
  const summaryParsed = SummaryConfigSchema.parse(config.summary ?? {});
8746
11785
  const compressParsed = CompressConfigSchema.parse(config.compress ?? {});
8747
11786
  const token = createTokenOperations(tokenParsed);
8748
- const summary = createSummaryOperations(config.llm, llm, token, summaryParsed);
8749
- const compress = createCompressOperations(compressParsed, token, summary, config.llm.maxTokens ?? 4096);
8750
- const context = createContextOperations(compressParsed, token, compress, contextStore, sessionStore, {
8751
- llm,
8752
- memory: memory ?? void 0,
8753
- rag: rag ?? void 0,
8754
- reasoning: reasoning ?? void 0,
8755
- summary
8756
- });
11787
+ const summary = {
11788
+ generate: (messages, options) => resourceCall(async () => createSummaryOperations((await llmConfig()).llm, llm, token, summaryParsed).generate(messages, options)),
11789
+ summarize: (messages, options) => resourceCall(async () => createSummaryOperations((await llmConfig()).llm, llm, token, summaryParsed).summarize(messages, options))
11790
+ };
11791
+ const compress = createCompressOperations(
11792
+ compressParsed,
11793
+ token,
11794
+ summary,
11795
+ config.llm.maxTokens ?? 4096
11796
+ );
11797
+ const context = createContextOperations(
11798
+ compressParsed,
11799
+ token,
11800
+ compress,
11801
+ contextStore,
11802
+ sessionStore,
11803
+ {
11804
+ llm,
11805
+ memory: memory ?? void 0,
11806
+ rag: rag ?? void 0,
11807
+ reasoning: reasoning ?? void 0,
11808
+ summary
11809
+ }
11810
+ );
8757
11811
  const persona = createPersonaOperations(personaStore);
8758
- const file = createFileOperations(config, llm);
8759
- const audio = createAudioOperations(config);
8760
- const image = createImageOperations(config);
8761
- return { a2aStores, llm, mcp, embedding, reasoning, rerank, retrieval, rag, knowledge, memory, persona, token, summary, compress, context, file, audio, image };
11812
+ const file = {
11813
+ parse: (request) => resourceCall(async () => createFileOperations(await llmConfig(), llm).parse(request)),
11814
+ parseText: (content, filename) => resourceCall(async () => createFileOperations(await llmConfig(), llm).parseText(content, filename))
11815
+ };
11816
+ let audioSnapshot = createAudioOperations(await audioConfig());
11817
+ async function currentAudio2() {
11818
+ audioSnapshot = createAudioOperations(await audioConfig());
11819
+ return audioSnapshot;
11820
+ }
11821
+ const audio = {
11822
+ models: {
11823
+ list: () => resourceCall(async () => {
11824
+ await currentAudio2();
11825
+ return audioModels.operations.list();
11826
+ }),
11827
+ get: (id) => resourceCall(async () => {
11828
+ await currentAudio2();
11829
+ return audioModels.operations.get(id);
11830
+ }),
11831
+ register: (input) => resourceCall(async () => {
11832
+ const result = await audioModels.operations.register(input);
11833
+ if (result.success)
11834
+ await currentAudio2();
11835
+ return result;
11836
+ }),
11837
+ update: (input, version) => resourceCall(async () => {
11838
+ const result = await audioModels.operations.update(input, version);
11839
+ if (result.success)
11840
+ await currentAudio2();
11841
+ return result;
11842
+ }),
11843
+ remove: (id, version) => resourceCall(async () => {
11844
+ const result = await audioModels.operations.remove(id, version);
11845
+ if (result.success)
11846
+ await currentAudio2();
11847
+ return result;
11848
+ })
11849
+ },
11850
+ transcribe: (request) => resourceCall(async () => (await currentAudio2()).transcribe(request)),
11851
+ synthesize: (request) => resourceCall(async () => (await currentAudio2()).synthesize(request)),
11852
+ async *transcribeStream(request) {
11853
+ yield* (await currentAudio2()).transcribeStream(request);
11854
+ },
11855
+ async *synthesizeStream(request) {
11856
+ yield* (await currentAudio2()).synthesizeStream(request);
11857
+ },
11858
+ getCapabilities: (request) => audioSnapshot.getCapabilities(request)
11859
+ };
11860
+ const image = {
11861
+ models: imageModels.operations,
11862
+ generate: (request) => resourceCall(
11863
+ async () => createImageOperations(await imageConfig()).generate(request)
11864
+ )
11865
+ };
11866
+ return {
11867
+ closeRegistries: () => {
11868
+ registriesOpen = false;
11869
+ },
11870
+ a2aStores,
11871
+ llm,
11872
+ acp,
11873
+ mcp,
11874
+ embedding,
11875
+ reasoning,
11876
+ rerank,
11877
+ retrieval,
11878
+ rag,
11879
+ knowledge,
11880
+ memory,
11881
+ persona,
11882
+ token,
11883
+ summary,
11884
+ compress,
11885
+ context,
11886
+ file,
11887
+ audio,
11888
+ image
11889
+ };
8762
11890
  }
8763
11891
  var WRITE_LEASE_MS = 3e5;
8764
11892
  var CAS_ATTEMPTS = 12;
@@ -9077,7 +12205,7 @@ function documentInfo(document) {
9077
12205
  }
9078
12206
 
9079
12207
  // src/store/providers/ai-store-provider-db.ts
9080
- var logger31 = core.logger.child({ module: "ai", scope: "store-provider-db" });
12208
+ var logger30 = core.logger.child({ module: "ai", scope: "store-provider-db" });
9081
12209
  function requireSqlSuccess(result) {
9082
12210
  if (!result.success)
9083
12211
  throw new Error(result.error.message, { cause: result.error });
@@ -9596,7 +12724,7 @@ function createDbStoreProvider(deps) {
9596
12724
  if (result.status === "rejected")
9597
12725
  throw result.reason;
9598
12726
  }
9599
- logger31.debug("DB store provider initialized", { tableCount: relStores.size });
12727
+ logger30.debug("DB store provider initialized", { tableCount: relStores.size });
9600
12728
  }
9601
12729
  },
9602
12730
  async close() {
@@ -9827,10 +12955,11 @@ function createMemoryStoreProvider() {
9827
12955
  }
9828
12956
 
9829
12957
  // src/ai-main.ts
9830
- var logger32 = core.logger.child({ module: "ai", scope: "main" });
12958
+ var logger31 = core.logger.child({ module: "ai", scope: "main" });
9831
12959
  var initInProgress = false;
9832
12960
  var currentConfig = null;
9833
12961
  var currentLLM = null;
12962
+ var currentACP = null;
9834
12963
  var currentMCP = null;
9835
12964
  var currentEmbedding = null;
9836
12965
  var currentReasoning = null;
@@ -9850,12 +12979,14 @@ var currentImage = null;
9850
12979
  var currentA2AConfig;
9851
12980
  var currentA2AImpl = null;
9852
12981
  var currentA2AStores = null;
12982
+ var currentRegistriesClose = null;
9853
12983
  var currentStoreProvider = null;
9854
12984
  var notInitialized = core.module.createNotInitializedKit(
9855
12985
  HaiAIError.NOT_INITIALIZED,
9856
12986
  () => aiM("ai_notInitialized")
9857
12987
  );
9858
12988
  var notInitializedLLM = {
12989
+ models: notInitialized.proxy(),
9859
12990
  chat: () => Promise.resolve(notInitialized.result()),
9860
12991
  async *chatStream() {
9861
12992
  throw notInitialized.error();
@@ -9869,7 +13000,16 @@ var notInitializedLLM = {
9869
13000
  },
9870
13001
  generateObject: () => Promise.resolve(notInitialized.result())
9871
13002
  };
13003
+ var notInitializedACP = {
13004
+ agents: notInitialized.proxy(),
13005
+ sessions: notInitialized.proxy(),
13006
+ workspaces: notInitialized.proxy(),
13007
+ connect: () => Promise.resolve(notInitialized.result()),
13008
+ close: async () => {
13009
+ }
13010
+ };
9872
13011
  var notInitializedMCP = {
13012
+ servers: notInitialized.proxy(),
9873
13013
  registerTool: () => notInitialized.result(),
9874
13014
  registerResource: () => notInitialized.result(),
9875
13015
  registerPrompt: () => notInitialized.result(),
@@ -9885,6 +13025,7 @@ var notInitializedReasoning = {
9885
13025
  }
9886
13026
  };
9887
13027
  var notInitializedRetrieval = {
13028
+ sources: notInitialized.proxy(),
9888
13029
  addSource: () => Promise.resolve(notInitialized.result()),
9889
13030
  removeSource: () => Promise.resolve(notInitialized.result()),
9890
13031
  listSources: () => Promise.resolve([]),
@@ -9919,6 +13060,7 @@ var notInitializedContext = {
9919
13060
  var notInitializedRerank = notInitialized.proxy();
9920
13061
  var notInitializedFile = notInitialized.proxy();
9921
13062
  var notInitializedAudio = {
13063
+ models: notInitialized.proxy(),
9922
13064
  transcribe: () => Promise.resolve(notInitialized.result()),
9923
13065
  async *transcribeStream() {
9924
13066
  throw notInitialized.error();
@@ -9930,6 +13072,7 @@ var notInitializedAudio = {
9930
13072
  getCapabilities: () => notInitialized.result()
9931
13073
  };
9932
13074
  var notInitializedImage = {
13075
+ models: notInitialized.proxy(),
9933
13076
  generate: () => Promise.resolve(notInitialized.result())
9934
13077
  };
9935
13078
  var a2aLazyOperations = createA2ALazyProxy({
@@ -9945,7 +13088,11 @@ var a2aLazyOperations = createA2ALazyProxy({
9945
13088
  async function resetAllState() {
9946
13089
  const storeProvider = currentStoreProvider;
9947
13090
  await currentA2AImpl?.close();
13091
+ await currentACP?.close();
9948
13092
  currentLLM = null;
13093
+ currentRegistriesClose?.();
13094
+ currentRegistriesClose = null;
13095
+ currentACP = null;
9949
13096
  currentMCP = null;
9950
13097
  currentEmbedding = null;
9951
13098
  currentReasoning = null;
@@ -9971,7 +13118,7 @@ async function resetAllState() {
9971
13118
  try {
9972
13119
  await storeProvider.close();
9973
13120
  } catch (error) {
9974
- logger32.error("AI store provider close failed", { error });
13121
+ logger31.error("AI store provider close failed", { error });
9975
13122
  }
9976
13123
  }
9977
13124
  }
@@ -9987,22 +13134,25 @@ var streamOperations = {
9987
13134
  };
9988
13135
  var ai = {
9989
13136
  async init(config, options) {
9990
- if (Object.keys(process.env).some((name) => /^HAI_AI_LLM_(?:MODELS_\d+_)?API$/.test(name)))
13137
+ if (Object.keys(process2.env).some((name) => /^HAI_AI_LLM_(?:MODELS_\d+_)?API$/.test(name)))
9991
13138
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_llmProtocolRenamed"));
9992
13139
  if (initInProgress) {
9993
- logger32.warn("AI init already in progress, skipping concurrent call");
13140
+ logger31.warn("AI init already in progress, skipping concurrent call");
9994
13141
  return err(HaiAIError.INIT_IN_PROGRESS, aiM("ai_initInProgress"));
9995
13142
  }
9996
13143
  initInProgress = true;
13144
+ let closingPrevious = false;
9997
13145
  try {
9998
13146
  if (currentConfig) {
9999
- logger32.warn("AI module is already initialized, reinitializing");
13147
+ logger31.warn("AI module is already initialized, reinitializing");
13148
+ closingPrevious = true;
10000
13149
  await ai.close();
13150
+ closingPrevious = false;
10001
13151
  }
10002
- logger32.info("Initializing AI module");
13152
+ logger31.info("Initializing AI module");
10003
13153
  const parseResult = AIConfigSchema.safeParse(config ?? {});
10004
13154
  if (!parseResult.success) {
10005
- logger32.error("AI config validation failed", { error: parseResult.error.message });
13155
+ logger31.error("AI config validation failed", { error: parseResult.error.message });
10006
13156
  return err(
10007
13157
  HaiAIError.CONFIGURATION_ERROR,
10008
13158
  aiM("ai_configError", { params: { error: parseResult.error.message } }),
@@ -10017,16 +13167,19 @@ var ai = {
10017
13167
  storeProvider = createDbStoreProviderFromModules();
10018
13168
  } else {
10019
13169
  storeProvider = createMemoryStoreProvider();
10020
- logger32.warn("AI is using the ephemeral in-memory store; initialize reldb and vecdb before ai.init() for persistent data");
13170
+ logger31.warn("AI is using the ephemeral in-memory store; initialize reldb and vecdb before ai.init() for persistent data");
10021
13171
  }
10022
- if (core.module.requiresSharedState(process.env) && storeProvider.stateScope !== "shared")
13172
+ if (core.module.requiresSharedState(process2.env) && storeProvider.stateScope !== "shared")
10023
13173
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_sharedBackendRequired"));
10024
13174
  currentStoreProvider = storeProvider;
10025
13175
  const subs = await createAISubsystems(parsed, {
10026
13176
  storeProvider,
10027
- datapipe
13177
+ datapipe,
13178
+ acpAdapters: options?.acpAdapters
10028
13179
  });
13180
+ currentRegistriesClose = subs.closeRegistries;
10029
13181
  currentLLM = subs.llm;
13182
+ currentACP = subs.acp;
10030
13183
  currentMCP = subs.mcp;
10031
13184
  currentEmbedding = subs.embedding;
10032
13185
  currentReasoning = subs.reasoning;
@@ -10046,11 +13199,17 @@ var ai = {
10046
13199
  currentA2AConfig = parsed.a2a;
10047
13200
  currentA2AStores = subs.a2aStores;
10048
13201
  currentConfig = parsed;
10049
- logger32.info("AI module initialized", { model: parsed.llm?.model });
13202
+ logger31.info("AI module initialized", { model: parsed.llm?.model });
10050
13203
  return ok(void 0);
10051
13204
  } catch (error) {
10052
- await resetAllState();
10053
- logger32.error("AI module initialization failed", { error });
13205
+ if (!closingPrevious) {
13206
+ try {
13207
+ await resetAllState();
13208
+ } catch (cleanupError) {
13209
+ logger31.error("AI initialization cleanup failed", { error: cleanupError });
13210
+ }
13211
+ }
13212
+ logger31.error("AI module initialization failed", { error });
10054
13213
  return err(
10055
13214
  HaiAIError.CONFIGURATION_ERROR,
10056
13215
  aiM("ai_initFailed", {
@@ -10065,6 +13224,9 @@ var ai = {
10065
13224
  get llm() {
10066
13225
  return currentLLM ?? notInitializedLLM;
10067
13226
  },
13227
+ get acp() {
13228
+ return currentACP ?? notInitializedACP;
13229
+ },
10068
13230
  get mcp() {
10069
13231
  return currentMCP ?? notInitializedMCP;
10070
13232
  },
@@ -10130,12 +13292,12 @@ var ai = {
10130
13292
  },
10131
13293
  async close() {
10132
13294
  if (!currentConfig) {
10133
- logger32.info("AI module already closed, skipping");
13295
+ logger31.info("AI module already closed, skipping");
10134
13296
  return;
10135
13297
  }
10136
- logger32.info("Closing AI module");
13298
+ logger31.info("Closing AI module");
10137
13299
  await resetAllState();
10138
- logger32.info("AI module closed");
13300
+ logger31.info("AI module closed");
10139
13301
  }
10140
13302
  };
10141
13303
  function createMcpServer(options) {