chainlesschain 0.166.132 → 0.166.133
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -18
- package/package.json +1 -1
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/data/changelog.json +13 -0
- package/src/gateways/ws/llm-chat-protocol.js +1 -1
- package/src/gateways/ws/personal-data-hub-protocol.js +27 -11
- package/src/gateways/ws/session-core-protocol.js +22 -17
- package/src/gateways/ws/session-protocol.js +19 -8
- package/src/gateways/ws/ws-agent-handler.js +30 -0
- package/src/lib/chat-core.js +40 -35
- package/src/lib/evolution/agent-model-projection.js +0 -1
- package/src/lib/evolution/governed-hub-llm.js +5 -1
- package/src/lib/evolution/governed-hub-resolver.js +15 -6
- package/src/lib/evolution/governed-hub-skill.js +2 -0
- package/src/lib/evolution/optional-ui-model-turn.js +13 -2
- package/src/lib/llm-config-defaults.js +17 -5
- package/src/lib/llm-connection-config.js +10 -2
- package/src/lib/llm-connection-probe.js +5 -2
- package/src/lib/llm-connection-resolution.js +7 -4
- package/src/lib/llm-providers.js +24 -11
- package/src/lib/openai-responses.js +2 -1
- package/src/lib/personal-data-hub-wiring.js +69 -36
- package/src/lib/provider-protocol.js +35 -3
- package/src/lib/provider-stream.js +6 -4
- package/src/lib/runnable-provider.js +4 -0
- package/src/lib/ws-chat-handler.js +22 -9
- package/src/runtime/agent-core.js +65 -6
|
@@ -77,8 +77,16 @@ function captureEmbeddingTransport(stage, sdk) {
|
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export function createGovernedHubResolver(
|
|
81
|
-
|
|
80
|
+
export function createGovernedHubResolver(
|
|
81
|
+
ports,
|
|
82
|
+
factory,
|
|
83
|
+
sdkPorts,
|
|
84
|
+
options = {},
|
|
85
|
+
) {
|
|
86
|
+
if (
|
|
87
|
+
typeof factory !== "function" &&
|
|
88
|
+
!(factory === null && options.evolutionDisabled === true)
|
|
89
|
+
)
|
|
82
90
|
throw new TypeError("A resolver evolution factory is required");
|
|
83
91
|
const {
|
|
84
92
|
resolver,
|
|
@@ -102,13 +110,14 @@ export function createGovernedHubResolver(ports, factory, sdkPorts) {
|
|
|
102
110
|
const turn = await prepareGovernedModelTurn(factory, {
|
|
103
111
|
mode: "hub-embedding",
|
|
104
112
|
messages: [{ role: "user", content: profile }],
|
|
113
|
+
evolutionDisabled: options.evolutionDisabled === true,
|
|
105
114
|
});
|
|
106
115
|
// Chat projection prepends host provenance. Embed only the projected
|
|
107
116
|
// profile: per-Run evidence IDs must not change semantic similarity.
|
|
108
|
-
const profileMessage = turn.messages[1];
|
|
117
|
+
const profileMessage = turn.messages[turn.governed ? 1 : 0];
|
|
109
118
|
if (
|
|
110
|
-
turn.messages.length !== 2 ||
|
|
111
|
-
turn.messages[0].role !== "system" ||
|
|
119
|
+
turn.messages.length !== (turn.governed ? 2 : 1) ||
|
|
120
|
+
(turn.governed && turn.messages[0].role !== "system") ||
|
|
112
121
|
profileMessage?.role !== "user" ||
|
|
113
122
|
typeof profileMessage.content !== "string"
|
|
114
123
|
)
|
|
@@ -133,7 +142,7 @@ export function createGovernedHubResolver(ports, factory, sdkPorts) {
|
|
|
133
142
|
const model =
|
|
134
143
|
resolver._llmStage &&
|
|
135
144
|
new LLMStage({
|
|
136
|
-
llm: createGovernedHubLlm(llm, factory,
|
|
145
|
+
llm: createGovernedHubLlm(llm, factory, options, sdk),
|
|
137
146
|
acceptNonLocal: false,
|
|
138
147
|
});
|
|
139
148
|
return new EntityResolver({
|
|
@@ -9,6 +9,7 @@ export async function runGovernedHubSkill(
|
|
|
9
9
|
name,
|
|
10
10
|
options,
|
|
11
11
|
sdkPorts,
|
|
12
|
+
modelTurnOptions = {},
|
|
12
13
|
) {
|
|
13
14
|
let failure;
|
|
14
15
|
let failed = false;
|
|
@@ -16,6 +17,7 @@ export async function runGovernedHubSkill(
|
|
|
16
17
|
hub.llm,
|
|
17
18
|
factory,
|
|
18
19
|
{
|
|
20
|
+
evolutionDisabled: modelTurnOptions.evolutionDisabled === true,
|
|
19
21
|
onFailure(error) {
|
|
20
22
|
failure = error;
|
|
21
23
|
failed = true;
|
|
@@ -12,9 +12,20 @@ export async function loadConfiguredUiEvolutionFactory(options = {}) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export async function resolveServerEvolutionFactory(server) {
|
|
15
|
-
return typeof server
|
|
15
|
+
return typeof server?.resolveEvolutionCompositionFactory === "function"
|
|
16
16
|
? server.resolveEvolutionCompositionFactory()
|
|
17
|
-
: (server
|
|
17
|
+
: (server?.evolutionCompositionFactory ?? null);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Apply the host's current UI policy for every turn, including old sessions. */
|
|
21
|
+
export async function prepareServerUiModelTurn(server, context) {
|
|
22
|
+
if (typeof server?.prepareUiModelTurn === "function") {
|
|
23
|
+
return server.prepareUiModelTurn(context);
|
|
24
|
+
}
|
|
25
|
+
return prepareGovernedModelTurn(
|
|
26
|
+
await resolveServerEvolutionFactory(server),
|
|
27
|
+
context,
|
|
28
|
+
);
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
export async function prepareConfiguredUiModelTurn(context, options = {}) {
|
|
@@ -35,7 +35,12 @@
|
|
|
35
35
|
* so an intentionally-custom endpoint is left untouched.
|
|
36
36
|
*/
|
|
37
37
|
export async function reconcileConfigLlmProvider(cfgLlm = {}) {
|
|
38
|
-
if (
|
|
38
|
+
if (
|
|
39
|
+
!cfgLlm ||
|
|
40
|
+
!cfgLlm.provider ||
|
|
41
|
+
!cfgLlm.baseUrl ||
|
|
42
|
+
cfgLlm.provider === "custom"
|
|
43
|
+
) {
|
|
39
44
|
return { llm: cfgLlm, changed: false };
|
|
40
45
|
}
|
|
41
46
|
const { inferProviderFromBaseUrl, modelForeignToProvider } = await import(
|
|
@@ -71,22 +76,29 @@ export function applyConfigLlmDefaults(options = {}, cfgLlm = {}, opts = {}) {
|
|
|
71
76
|
// credentials are exactly right. Mixing config into a DIFFERENT explicit
|
|
72
77
|
// provider is still avoided (handled by the early return below).
|
|
73
78
|
if (options.provider && configMatchesTarget) {
|
|
74
|
-
if (!options.baseUrl && cfgLlm.baseUrl)
|
|
79
|
+
if (!options.baseUrl && cfgLlm.baseUrl)
|
|
80
|
+
options.baseUrl = connection.baseUrl;
|
|
75
81
|
if (options.serviceTier == null && cfgLlm.serviceTier != null)
|
|
76
82
|
options.serviceTier = cfgLlm.serviceTier;
|
|
77
|
-
if (!options.apiKey && connection.apiKey)
|
|
83
|
+
if (!options.apiKey && connection.apiKey)
|
|
84
|
+
options.apiKey = connection.apiKey;
|
|
78
85
|
if (!options.model && !opts.explicitModel && cfgLlm.model) {
|
|
79
86
|
options.model = cfgLlm.model;
|
|
80
87
|
}
|
|
81
88
|
return options;
|
|
82
89
|
}
|
|
83
90
|
if (options.provider) {
|
|
84
|
-
if (!options.apiKey && connection.apiKey)
|
|
91
|
+
if (!options.apiKey && connection.apiKey)
|
|
92
|
+
options.apiKey = connection.apiKey;
|
|
85
93
|
return options;
|
|
86
94
|
}
|
|
87
95
|
if (!cfgLlm.provider) return options;
|
|
88
96
|
options.provider = connection.provider;
|
|
89
|
-
if (
|
|
97
|
+
if (
|
|
98
|
+
configMatchesTarget &&
|
|
99
|
+
options.serviceTier == null &&
|
|
100
|
+
cfgLlm.serviceTier != null
|
|
101
|
+
)
|
|
90
102
|
options.serviceTier = cfgLlm.serviceTier;
|
|
91
103
|
if (opts.explicitModel) {
|
|
92
104
|
options.model = opts.explicitModel;
|
|
@@ -31,7 +31,10 @@ export function normalizeLlmConnection(input) {
|
|
|
31
31
|
if (value[key].length > (key === "apiKey" ? 8192 : 2048))
|
|
32
32
|
throw new Error(`${key} is too long`);
|
|
33
33
|
}
|
|
34
|
-
if (
|
|
34
|
+
if (
|
|
35
|
+
value.provider !== "custom" &&
|
|
36
|
+
!Object.hasOwn(BUILT_IN_PROVIDERS, value.provider)
|
|
37
|
+
)
|
|
35
38
|
throw new Error("Choose a supported provider/protocol");
|
|
36
39
|
if (!value.model || /[\r\n\0]/.test(value.model + value.visionModel))
|
|
37
40
|
throw new Error("A valid model name is required");
|
|
@@ -84,7 +87,7 @@ export function saveLlmConnection(input, { storage = "auto" } = {}) {
|
|
|
84
87
|
current.baseUrl || BUILT_IN_PROVIDERS[current.provider]?.baseUrl || "",
|
|
85
88
|
).replace(/\/+$/, "") === value.baseUrl;
|
|
86
89
|
if (
|
|
87
|
-
value.provider
|
|
90
|
+
!["ollama", "custom"].includes(value.provider) &&
|
|
88
91
|
!value.apiKey &&
|
|
89
92
|
(!sameEndpoint || !current.apiKey)
|
|
90
93
|
)
|
|
@@ -98,6 +101,11 @@ export function saveLlmConnection(input, { storage = "auto" } = {}) {
|
|
|
98
101
|
baseUrl: value.baseUrl,
|
|
99
102
|
visionModel: value.visionModel || null,
|
|
100
103
|
};
|
|
104
|
+
// A keyless custom endpoint must never receive the previous target's key.
|
|
105
|
+
if (!sameEndpoint && !value.apiKey && value.provider === "custom") {
|
|
106
|
+
delete config.llm.apiKey;
|
|
107
|
+
delete config.llm.apiKeyHelper;
|
|
108
|
+
}
|
|
101
109
|
};
|
|
102
110
|
// A single lock and atomic rename bind endpoint and credential together.
|
|
103
111
|
// An interrupted save cannot route the old credential to the new endpoint.
|
|
@@ -3,6 +3,7 @@ import { estimateCost } from "./llm-pricing.js";
|
|
|
3
3
|
import { providerFetch } from "./formal-budget-provider-scope.js";
|
|
4
4
|
import { isFormalBudgetError } from "./formal-budget-errors.js";
|
|
5
5
|
import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
|
|
6
|
+
import { assertCustomProviderConnection } from "./provider-protocol.js";
|
|
6
7
|
import { resolveModelCapabilityProfile } from "./model-capabilities.js";
|
|
7
8
|
import {
|
|
8
9
|
projectEnvelopePricingContext,
|
|
@@ -159,6 +160,7 @@ export async function probeLlmConnection(
|
|
|
159
160
|
target,
|
|
160
161
|
{ fetchImpl = fetch, timeoutMs = 20_000, includeUsage = false } = {},
|
|
161
162
|
) {
|
|
163
|
+
assertCustomProviderConnection(target.provider, target);
|
|
162
164
|
const base =
|
|
163
165
|
target.provider === "ollama"
|
|
164
166
|
? normalizeOllamaBaseUrl(target.baseUrl)
|
|
@@ -183,7 +185,8 @@ export async function probeLlmConnection(
|
|
|
183
185
|
options: { num_predict: PROBE_OUTPUT_TOKENS },
|
|
184
186
|
};
|
|
185
187
|
} else {
|
|
186
|
-
if (!target.apiKey
|
|
188
|
+
if (!target.apiKey && target.provider !== "custom")
|
|
189
|
+
throw new Error("API key required");
|
|
187
190
|
if (target.provider === "anthropic") {
|
|
188
191
|
url = `${base}/messages`;
|
|
189
192
|
headers["x-api-key"] = target.apiKey;
|
|
@@ -211,7 +214,7 @@ export async function probeLlmConnection(
|
|
|
211
214
|
});
|
|
212
215
|
} else {
|
|
213
216
|
url = `${base}/chat/completions`;
|
|
214
|
-
headers.Authorization = `Bearer ${target.apiKey}`;
|
|
217
|
+
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
215
218
|
body = {
|
|
216
219
|
model: target.model,
|
|
217
220
|
messages: [{ role: "user", content: "Say hi in one word." }],
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
|
|
2
2
|
import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
assertCustomProviderConnection,
|
|
5
|
+
normalizeProviderBaseUrl,
|
|
6
|
+
} from "./provider-protocol.js";
|
|
4
7
|
import { resolveApiKeyFromHelper } from "./api-key-helper.js";
|
|
5
8
|
|
|
6
9
|
const DEFAULT_PROVIDER = "ollama";
|
|
@@ -33,9 +36,8 @@ export function resolveLlmConnection(
|
|
|
33
36
|
defaultBaseUrl = null,
|
|
34
37
|
} = {},
|
|
35
38
|
) {
|
|
36
|
-
const sources = (
|
|
37
|
-
? configuredSources
|
|
38
|
-
: [configuredSources]
|
|
39
|
+
const sources = (
|
|
40
|
+
Array.isArray(configuredSources) ? configuredSources : [configuredSources]
|
|
39
41
|
).filter((source) => source && typeof source === "object");
|
|
40
42
|
const provider =
|
|
41
43
|
(nonEmpty(options.provider) && options.provider) ||
|
|
@@ -67,6 +69,7 @@ export function resolveLlmConnection(
|
|
|
67
69
|
defaultModel ||
|
|
68
70
|
definition?.models?.[0] ||
|
|
69
71
|
null;
|
|
72
|
+
assertCustomProviderConnection(provider, { baseUrl, model });
|
|
70
73
|
let apiKey =
|
|
71
74
|
(nonEmpty(options.apiKey) && options.apiKey) ||
|
|
72
75
|
(nonEmpty(env?.CC_API_KEY) && env.CC_API_KEY) ||
|
package/src/lib/llm-providers.js
CHANGED
|
@@ -127,6 +127,19 @@ export const BUILT_IN_PROVIDERS = {
|
|
|
127
127
|
},
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
+
/** Custom platforms use Chat Completions with an explicit endpoint and model. */
|
|
131
|
+
export function resolveProviderDefinition(provider) {
|
|
132
|
+
return provider === "custom"
|
|
133
|
+
? {
|
|
134
|
+
name: "custom",
|
|
135
|
+
baseUrl: null,
|
|
136
|
+
models: [],
|
|
137
|
+
apiKeyEnv: "CC_API_KEY",
|
|
138
|
+
custom: true,
|
|
139
|
+
}
|
|
140
|
+
: BUILT_IN_PROVIDERS[provider];
|
|
141
|
+
}
|
|
142
|
+
|
|
130
143
|
// A connectivity probe (`testProvider`) sends a tiny "Hi" and expects a quick
|
|
131
144
|
// reply, so it must fail fast — a probe that hangs forever defeats its purpose
|
|
132
145
|
// (it backs `cc llm test`, provider selection, doctor). Bound it with a total
|
|
@@ -402,17 +415,17 @@ export class LLMProviderRegistry {
|
|
|
402
415
|
const res = await _fetchWithTimeout(
|
|
403
416
|
`${normalizeProviderBaseUrl(provider.baseUrl)}/messages`,
|
|
404
417
|
{
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
418
|
+
method: "POST",
|
|
419
|
+
headers: {
|
|
420
|
+
"Content-Type": "application/json",
|
|
421
|
+
"x-api-key": key,
|
|
422
|
+
"anthropic-version": "2023-06-01",
|
|
423
|
+
},
|
|
424
|
+
body: JSON.stringify({
|
|
425
|
+
model: testModel,
|
|
426
|
+
max_tokens: 10,
|
|
427
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
428
|
+
}),
|
|
416
429
|
},
|
|
417
430
|
);
|
|
418
431
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
@@ -103,7 +103,8 @@ function sanitizeReasoningItems(items) {
|
|
|
103
103
|
type: "reasoning",
|
|
104
104
|
...(id ? { id } : {}),
|
|
105
105
|
...(encryptedContent ? { encrypted_content: encryptedContent } : {}),
|
|
106
|
-
|
|
106
|
+
// Responses requires this field even when no summary was requested.
|
|
107
|
+
summary,
|
|
107
108
|
...(status ? { status } : {}),
|
|
108
109
|
});
|
|
109
110
|
}
|
|
@@ -644,35 +644,44 @@ async function initHub() {
|
|
|
644
644
|
// collector can pull WebView cookies from the user's Android Bilibili
|
|
645
645
|
// App. The factory is a pure function — no side effects until the
|
|
646
646
|
// handler is invoked, so cost of always-registering is zero.
|
|
647
|
-
const { createBilibiliCookiesExtension } =
|
|
648
|
-
|
|
647
|
+
const { createBilibiliCookiesExtension } = await import(
|
|
648
|
+
"@chainlesschain/personal-data-hub/adapters/social-bilibili-adb"
|
|
649
|
+
);
|
|
649
650
|
// Phase 2a: register `douyin.pull-im-db` extension so the
|
|
650
651
|
// douyinAdbSync hub method can pull <uid>_im.db cohort from the
|
|
651
652
|
// user's Android Douyin App.
|
|
652
653
|
const { createDouyinDbExtension, createDouyinWatchExtension } =
|
|
653
|
-
await import(
|
|
654
|
+
await import(
|
|
655
|
+
"@chainlesschain/personal-data-hub/adapters/social-douyin-adb"
|
|
656
|
+
);
|
|
654
657
|
// Phase 3a: register `weibo.cookies` extension for the Weibo
|
|
655
658
|
// C-path collector (m.weibo.cn cookies + 4 HTTP endpoints).
|
|
656
|
-
const { createWeiboCookiesExtension } =
|
|
657
|
-
|
|
659
|
+
const { createWeiboCookiesExtension } = await import(
|
|
660
|
+
"@chainlesschain/personal-data-hub/adapters/social-weibo-adb"
|
|
661
|
+
);
|
|
658
662
|
// Phase 3c: register `xhs.cookies` extension for the Xhs C-path
|
|
659
663
|
// collector (xiaohongshu.com cookies + 4 endpoints with X-S sign).
|
|
660
|
-
const { createXhsCookiesExtension } =
|
|
661
|
-
|
|
664
|
+
const { createXhsCookiesExtension } = await import(
|
|
665
|
+
"@chainlesschain/personal-data-hub/adapters/social-xiaohongshu-adb"
|
|
666
|
+
);
|
|
662
667
|
// Phase 6c: register `toutiao.cookies` extension for the Toutiao
|
|
663
668
|
// C-path collector (www.toutiao.com cookies + 4 endpoints, 3 of
|
|
664
669
|
// which need _signature signing — CLI context falls back to -99
|
|
665
670
|
// short-circuit, desktop wiring upgrades via ToutiaoSignBridge).
|
|
666
671
|
const { createToutiaoCookiesExtension, createToutiaoAccountExtension } =
|
|
667
|
-
await import(
|
|
672
|
+
await import(
|
|
673
|
+
"@chainlesschain/personal-data-hub/adapters/social-toutiao-adb"
|
|
674
|
+
);
|
|
668
675
|
// Phase 6d: register `kuaishou.cookies` extension. Profile from
|
|
669
676
|
// cookie's api_ph payload (no HTTP); 3 GraphQL POST endpoints need
|
|
670
677
|
// __NS_sig3 + kpf/kpn — CLI short-circuits, desktop upgrades via
|
|
671
678
|
// KuaishouSignBridge.
|
|
672
|
-
const { createKuaishouCookiesExtension } =
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
679
|
+
const { createKuaishouCookiesExtension } = await import(
|
|
680
|
+
"@chainlesschain/personal-data-hub/adapters/social-kuaishou-adb"
|
|
681
|
+
);
|
|
682
|
+
const { createWhatsAppBackupExtension } = await import(
|
|
683
|
+
"@chainlesschain/personal-data-hub/adapters/messaging-whatsapp"
|
|
684
|
+
);
|
|
676
685
|
hostAdbBridge = createHostAdbBridge({
|
|
677
686
|
extensions: {
|
|
678
687
|
"bilibili.cookies": createBilibiliCookiesExtension(),
|
|
@@ -1105,16 +1114,20 @@ async function initHub() {
|
|
|
1105
1114
|
alipayAccountsPath,
|
|
1106
1115
|
entityResolver,
|
|
1107
1116
|
aichatAccountsStore,
|
|
1108
|
-
async drainResolver(options = {}, factory = null) {
|
|
1109
|
-
if (
|
|
1117
|
+
async drainResolver(options = {}, factory = null, modelTurnOptions = {}) {
|
|
1118
|
+
if (
|
|
1119
|
+
typeof factory !== "function" &&
|
|
1120
|
+
!(factory === null && modelTurnOptions.evolutionDisabled === true)
|
|
1121
|
+
) {
|
|
1110
1122
|
const error = new Error(
|
|
1111
1123
|
"Personal Data Hub resolver egress requires an authenticated evolution composition factory",
|
|
1112
1124
|
);
|
|
1113
1125
|
error.code = "CC_AGENT_EVOLUTION_INGRESS_FAILED";
|
|
1114
1126
|
throw error;
|
|
1115
1127
|
}
|
|
1116
|
-
const { createGovernedHubResolver } =
|
|
1117
|
-
|
|
1128
|
+
const { createGovernedHubResolver } = await import(
|
|
1129
|
+
"./evolution/governed-hub-resolver.js"
|
|
1130
|
+
);
|
|
1118
1131
|
const scoped = createGovernedHubResolver(
|
|
1119
1132
|
{
|
|
1120
1133
|
resolver: entityResolver,
|
|
@@ -1125,6 +1138,8 @@ async function initHub() {
|
|
|
1125
1138
|
LLMStage: EntityResolverLLMStage,
|
|
1126
1139
|
},
|
|
1127
1140
|
factory,
|
|
1141
|
+
undefined,
|
|
1142
|
+
modelTurnOptions,
|
|
1128
1143
|
);
|
|
1129
1144
|
return scoped.drain(options);
|
|
1130
1145
|
},
|
|
@@ -1656,8 +1671,9 @@ async function initHub() {
|
|
|
1656
1671
|
}
|
|
1657
1672
|
let collector;
|
|
1658
1673
|
try {
|
|
1659
|
-
const mod =
|
|
1660
|
-
|
|
1674
|
+
const mod = await import(
|
|
1675
|
+
"@chainlesschain/personal-data-hub/adapters/social-bilibili-adb"
|
|
1676
|
+
);
|
|
1661
1677
|
collector = mod.default ? mod.default : mod;
|
|
1662
1678
|
} catch (err) {
|
|
1663
1679
|
return {
|
|
@@ -1708,8 +1724,9 @@ async function initHub() {
|
|
|
1708
1724
|
}
|
|
1709
1725
|
let collector;
|
|
1710
1726
|
try {
|
|
1711
|
-
const mod =
|
|
1712
|
-
|
|
1727
|
+
const mod = await import(
|
|
1728
|
+
"@chainlesschain/personal-data-hub/adapters/social-douyin-adb"
|
|
1729
|
+
);
|
|
1713
1730
|
collector = mod.default ? mod.default : mod;
|
|
1714
1731
|
} catch (err) {
|
|
1715
1732
|
return {
|
|
@@ -1752,8 +1769,9 @@ async function initHub() {
|
|
|
1752
1769
|
}
|
|
1753
1770
|
let collector;
|
|
1754
1771
|
try {
|
|
1755
|
-
const mod =
|
|
1756
|
-
|
|
1772
|
+
const mod = await import(
|
|
1773
|
+
"@chainlesschain/personal-data-hub/adapters/social-douyin-adb"
|
|
1774
|
+
);
|
|
1757
1775
|
collector = mod.default ? mod.default : mod;
|
|
1758
1776
|
} catch (err) {
|
|
1759
1777
|
return {
|
|
@@ -1799,8 +1817,9 @@ async function initHub() {
|
|
|
1799
1817
|
}
|
|
1800
1818
|
let collector;
|
|
1801
1819
|
try {
|
|
1802
|
-
const mod =
|
|
1803
|
-
|
|
1820
|
+
const mod = await import(
|
|
1821
|
+
"@chainlesschain/personal-data-hub/adapters/social-weibo-adb"
|
|
1822
|
+
);
|
|
1804
1823
|
collector = mod.default ? mod.default : mod;
|
|
1805
1824
|
} catch (err) {
|
|
1806
1825
|
return {
|
|
@@ -1852,8 +1871,9 @@ async function initHub() {
|
|
|
1852
1871
|
}
|
|
1853
1872
|
let collector;
|
|
1854
1873
|
try {
|
|
1855
|
-
const mod =
|
|
1856
|
-
|
|
1874
|
+
const mod = await import(
|
|
1875
|
+
"@chainlesschain/personal-data-hub/adapters/social-xiaohongshu-adb"
|
|
1876
|
+
);
|
|
1857
1877
|
collector = mod.default ? mod.default : mod;
|
|
1858
1878
|
} catch (err) {
|
|
1859
1879
|
return {
|
|
@@ -1905,8 +1925,9 @@ async function initHub() {
|
|
|
1905
1925
|
}
|
|
1906
1926
|
let collector;
|
|
1907
1927
|
try {
|
|
1908
|
-
const mod =
|
|
1909
|
-
|
|
1928
|
+
const mod = await import(
|
|
1929
|
+
"@chainlesschain/personal-data-hub/adapters/social-toutiao-adb"
|
|
1930
|
+
);
|
|
1910
1931
|
collector = mod.default ? mod.default : mod;
|
|
1911
1932
|
} catch (err) {
|
|
1912
1933
|
return {
|
|
@@ -1960,8 +1981,9 @@ async function initHub() {
|
|
|
1960
1981
|
}
|
|
1961
1982
|
let collector;
|
|
1962
1983
|
try {
|
|
1963
|
-
const mod =
|
|
1964
|
-
|
|
1984
|
+
const mod = await import(
|
|
1985
|
+
"@chainlesschain/personal-data-hub/adapters/social-kuaishou-adb"
|
|
1986
|
+
);
|
|
1965
1987
|
collector = mod.default ? mod.default : mod;
|
|
1966
1988
|
} catch (err) {
|
|
1967
1989
|
return {
|
|
@@ -2149,11 +2171,19 @@ export async function getHub() {
|
|
|
2149
2171
|
return _initPromise;
|
|
2150
2172
|
}
|
|
2151
2173
|
|
|
2152
|
-
export async function getGovernedAnalysisHub(
|
|
2174
|
+
export async function getGovernedAnalysisHub(
|
|
2175
|
+
evolutionCompositionFactory,
|
|
2176
|
+
modelTurnOptions = {},
|
|
2177
|
+
) {
|
|
2153
2178
|
const hub = await getHub();
|
|
2154
|
-
const { createGovernedHubLlm } =
|
|
2155
|
-
|
|
2156
|
-
|
|
2179
|
+
const { createGovernedHubLlm } = await import(
|
|
2180
|
+
"./evolution/governed-hub-llm.js"
|
|
2181
|
+
);
|
|
2182
|
+
const llm = createGovernedHubLlm(
|
|
2183
|
+
hub.engine.llm,
|
|
2184
|
+
evolutionCompositionFactory,
|
|
2185
|
+
modelTurnOptions,
|
|
2186
|
+
);
|
|
2157
2187
|
const engine = new AnalysisEngine({
|
|
2158
2188
|
vault: hub.engine.vault,
|
|
2159
2189
|
llm,
|
|
@@ -2162,8 +2192,9 @@ export async function getGovernedAnalysisHub(evolutionCompositionFactory) {
|
|
|
2162
2192
|
maxQueryLimit: hub.engine.maxQueryLimit,
|
|
2163
2193
|
systemPrompt: hub.engine.systemPrompt,
|
|
2164
2194
|
});
|
|
2165
|
-
const { runGovernedHubSkill } =
|
|
2166
|
-
|
|
2195
|
+
const { runGovernedHubSkill } = await import(
|
|
2196
|
+
"./evolution/governed-hub-skill.js"
|
|
2197
|
+
);
|
|
2167
2198
|
return Object.freeze({
|
|
2168
2199
|
...hub,
|
|
2169
2200
|
llm,
|
|
@@ -2175,6 +2206,8 @@ export async function getGovernedAnalysisHub(evolutionCompositionFactory) {
|
|
|
2175
2206
|
runAnalysisSkill,
|
|
2176
2207
|
name,
|
|
2177
2208
|
options,
|
|
2209
|
+
undefined,
|
|
2210
|
+
modelTurnOptions,
|
|
2178
2211
|
),
|
|
2179
2212
|
});
|
|
2180
2213
|
}
|
|
@@ -4,6 +4,29 @@ export function normalizeProviderBaseUrl(baseUrl) {
|
|
|
4
4
|
return String(baseUrl || "").replace(/\/+$/u, "");
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
/** Validate custom targets without assuming a vendor, path, model, or key. */
|
|
8
|
+
export function assertCustomProviderConnection(provider, { baseUrl, model }) {
|
|
9
|
+
if (provider !== "custom") return;
|
|
10
|
+
let url;
|
|
11
|
+
try {
|
|
12
|
+
url = new URL(baseUrl);
|
|
13
|
+
} catch {
|
|
14
|
+
throw new Error("Custom provider requires an HTTP(S) API base URL");
|
|
15
|
+
}
|
|
16
|
+
if (
|
|
17
|
+
!["http:", "https:"].includes(url.protocol) ||
|
|
18
|
+
url.username ||
|
|
19
|
+
url.password ||
|
|
20
|
+
url.search ||
|
|
21
|
+
url.hash
|
|
22
|
+
)
|
|
23
|
+
throw new Error(
|
|
24
|
+
"Custom provider requires an HTTP(S) API base URL without credentials, query or fragment",
|
|
25
|
+
);
|
|
26
|
+
if (typeof model !== "string" || !model.trim())
|
|
27
|
+
throw new Error("Custom provider requires an explicit model name");
|
|
28
|
+
}
|
|
29
|
+
|
|
7
30
|
/**
|
|
8
31
|
* Google's OpenAI-compatible API lives below `/v1beta/openai`. The provider
|
|
9
32
|
* registry stores the native API root (`/v1beta`), so adapt only that exact
|
|
@@ -28,7 +51,11 @@ export function openAICompatibleBaseUrl(provider, baseUrl) {
|
|
|
28
51
|
return base;
|
|
29
52
|
}
|
|
30
53
|
|
|
31
|
-
export function geminiGenerateContentUrl(
|
|
54
|
+
export function geminiGenerateContentUrl(
|
|
55
|
+
baseUrl,
|
|
56
|
+
model,
|
|
57
|
+
{ stream = false } = {},
|
|
58
|
+
) {
|
|
32
59
|
const action = stream ? "streamGenerateContent" : "generateContent";
|
|
33
60
|
const endpoint = `${normalizeProviderBaseUrl(baseUrl)}/models/${encodeURIComponent(model)}:${action}`;
|
|
34
61
|
return stream ? `${endpoint}?alt=sse` : endpoint;
|
|
@@ -58,7 +85,10 @@ export function geminiRequestBody(
|
|
|
58
85
|
continue;
|
|
59
86
|
}
|
|
60
87
|
contents.push({
|
|
61
|
-
role:
|
|
88
|
+
role:
|
|
89
|
+
message.role === "assistant" || message.role === "model"
|
|
90
|
+
? "model"
|
|
91
|
+
: "user",
|
|
62
92
|
parts,
|
|
63
93
|
});
|
|
64
94
|
}
|
|
@@ -66,7 +96,9 @@ export function geminiRequestBody(
|
|
|
66
96
|
if (Number.isFinite(temperature)) generationConfig.temperature = temperature;
|
|
67
97
|
return {
|
|
68
98
|
contents,
|
|
69
|
-
...(systemParts.length
|
|
99
|
+
...(systemParts.length
|
|
100
|
+
? { systemInstruction: { parts: systemParts } }
|
|
101
|
+
: {}),
|
|
70
102
|
generationConfig,
|
|
71
103
|
};
|
|
72
104
|
}
|
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
* of token deltas suitable for piping through session-core StreamRouter.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { resolveProviderDefinition } from "./llm-providers.js";
|
|
12
12
|
import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
|
|
13
13
|
import { ollamaProviderFetch } from "./ollama-provider-fetch.js";
|
|
14
14
|
import {
|
|
15
|
+
assertCustomProviderConnection,
|
|
15
16
|
geminiGenerateContentUrl,
|
|
16
17
|
geminiRequestBody,
|
|
17
18
|
normalizeProviderBaseUrl,
|
|
@@ -111,7 +112,7 @@ export async function* openAIStream({
|
|
|
111
112
|
method: "POST",
|
|
112
113
|
headers: {
|
|
113
114
|
"Content-Type": "application/json",
|
|
114
|
-
Authorization: `Bearer ${apiKey}
|
|
115
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
115
116
|
},
|
|
116
117
|
body: JSON.stringify({
|
|
117
118
|
model,
|
|
@@ -276,6 +277,7 @@ export async function* anthropicTokenStream({
|
|
|
276
277
|
* Throws on unsupported provider / missing API key.
|
|
277
278
|
*/
|
|
278
279
|
export function buildProviderSource(provider, opts = {}) {
|
|
280
|
+
assertCustomProviderConnection(provider, opts);
|
|
279
281
|
const {
|
|
280
282
|
model,
|
|
281
283
|
baseUrl,
|
|
@@ -295,11 +297,11 @@ export function buildProviderSource(provider, opts = {}) {
|
|
|
295
297
|
requireCompletion,
|
|
296
298
|
});
|
|
297
299
|
}
|
|
298
|
-
const def =
|
|
300
|
+
const def = resolveProviderDefinition(provider);
|
|
299
301
|
if (!def) throw new Error(`Unsupported provider: ${provider}`);
|
|
300
302
|
const finalKey =
|
|
301
303
|
apiKey || (def.apiKeyEnv ? process.env[def.apiKeyEnv] : null);
|
|
302
|
-
if (!finalKey) {
|
|
304
|
+
if (!finalKey && provider !== "custom") {
|
|
303
305
|
throw new Error(
|
|
304
306
|
`API key required for ${provider} (--api-key or ${def.apiKeyEnv})`,
|
|
305
307
|
);
|
|
@@ -146,6 +146,8 @@ export function makeRunnableProviderFallback(
|
|
|
146
146
|
// react to an auth failure AND pre-empt the known-bad attempt on later turns.
|
|
147
147
|
const computeFallback = (options) => {
|
|
148
148
|
const failed = options.provider;
|
|
149
|
+
// A custom protocol selection is intentional, including on localhost.
|
|
150
|
+
if (failed === "custom") return null;
|
|
149
151
|
const inferred = inferProviderFromBaseUrl(options.baseUrl);
|
|
150
152
|
if (inferred && inferred !== failed) {
|
|
151
153
|
return {
|
|
@@ -269,6 +271,8 @@ export function hasUsableKey(
|
|
|
269
271
|
provider,
|
|
270
272
|
{ apiKey, isActive = true, env = process.env } = {},
|
|
271
273
|
) {
|
|
274
|
+
// Custom platforms may be keyless; connection validation checks their target.
|
|
275
|
+
if (provider === "custom") return true;
|
|
272
276
|
const def = BUILT_IN_PROVIDERS[provider];
|
|
273
277
|
if (!def) return false;
|
|
274
278
|
if (!def.apiKeyEnv) return true; // keyless local provider (e.g. ollama)
|