deepseek-coordination 0.10.0 → 0.11.0
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 +52 -25
- package/README.zh-CN.md +51 -24
- package/lib/client.js +207 -1524
- package/lib/index.d.mts +67 -1
- package/lib/index.mjs +860 -122
- package/package.json +11 -11
package/lib/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
1
3
|
import Schema$1 from "@deepseek-ai/schemastery";
|
|
2
4
|
import { Service } from "@deepseek-ai/cordis";
|
|
3
|
-
import { closeSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, statSync } from "node:fs";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
|
-
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
6
6
|
import { DatabaseSync } from "node:sqlite";
|
|
7
7
|
import { createHash, randomBytes } from "node:crypto";
|
|
8
8
|
//#region src/shared/enum.ts
|
|
@@ -4428,18 +4428,6 @@ const PROFESSION_CONTROLS = [
|
|
|
4428
4428
|
];
|
|
4429
4429
|
/** Fields whose six defaults differ, therefore one row per scene. */
|
|
4430
4430
|
const SCENE_FIELDS = [
|
|
4431
|
-
{
|
|
4432
|
-
field: "thinking",
|
|
4433
|
-
copy: "scene.thinking",
|
|
4434
|
-
kind: "enum",
|
|
4435
|
-
options: ["enabled", "disabled"]
|
|
4436
|
-
},
|
|
4437
|
-
{
|
|
4438
|
-
field: "reasoningEffort",
|
|
4439
|
-
copy: "scene.reasoningEffort",
|
|
4440
|
-
kind: "text",
|
|
4441
|
-
wired: true
|
|
4442
|
-
},
|
|
4443
4431
|
{
|
|
4444
4432
|
field: "requirePlan",
|
|
4445
4433
|
copy: "scene.requirePlan",
|
|
@@ -5813,9 +5801,11 @@ var SelectionModule = class {
|
|
|
5813
5801
|
} catch {
|
|
5814
5802
|
spec = {};
|
|
5815
5803
|
}
|
|
5804
|
+
const rawName = typeof option.name === "string" ? option.name.trim() : "";
|
|
5805
|
+
const name = rawName.length > 0 && rawName !== option.value ? rawName : option.value in PermissionLabel ? PermissionLabel[option.value] : option.value;
|
|
5816
5806
|
return {
|
|
5817
5807
|
value: option.value,
|
|
5818
|
-
name
|
|
5808
|
+
name,
|
|
5819
5809
|
description: option.description ?? spec.description,
|
|
5820
5810
|
...spec.sandbox === void 0 ? {} : { sandbox: spec.sandbox },
|
|
5821
5811
|
...spec.approval === void 0 ? {} : { approval: spec.approval }
|
|
@@ -6754,7 +6744,7 @@ var CompactModule = class {
|
|
|
6754
6744
|
* (JSON for objects, String for the rest), so no DSH payload is assumed.
|
|
6755
6745
|
*/
|
|
6756
6746
|
function readSnapshot(result) {
|
|
6757
|
-
const record = asRecord$
|
|
6747
|
+
const record = asRecord$4(result);
|
|
6758
6748
|
if (record !== void 0) {
|
|
6759
6749
|
const hasBefore = Object.hasOwn(record, "before");
|
|
6760
6750
|
const hasAfter = Object.hasOwn(record, "after");
|
|
@@ -6774,7 +6764,7 @@ function readSnapshot(result) {
|
|
|
6774
6764
|
after: portText(result)
|
|
6775
6765
|
};
|
|
6776
6766
|
}
|
|
6777
|
-
function asRecord$
|
|
6767
|
+
function asRecord$4(value) {
|
|
6778
6768
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
6779
6769
|
}
|
|
6780
6770
|
/** Turn a port value into recordable text without trusting its shape. */
|
|
@@ -8448,12 +8438,12 @@ var TakeoverGate = class {
|
|
|
8448
8438
|
* agent. Returns a disposer; unload delegates every open request.
|
|
8449
8439
|
*/
|
|
8450
8440
|
install(context) {
|
|
8451
|
-
const register = asFunction$1(asRecord$
|
|
8441
|
+
const register = asFunction$1(asRecord$3(context)?.on);
|
|
8452
8442
|
if (register === void 0) return () => {};
|
|
8453
8443
|
const disposers = [];
|
|
8454
8444
|
const subscribe = (name, listener) => {
|
|
8455
8445
|
try {
|
|
8456
|
-
const dispose = register.call(context, name, listener);
|
|
8446
|
+
const dispose = register.call(context, name, listener, { prepend: true });
|
|
8457
8447
|
if (typeof dispose === "function") disposers.push(dispose);
|
|
8458
8448
|
} catch {}
|
|
8459
8449
|
};
|
|
@@ -8531,7 +8521,7 @@ var TakeoverGate = class {
|
|
|
8531
8521
|
*/
|
|
8532
8522
|
async handleApproval(request, next, signal) {
|
|
8533
8523
|
if (this.config.ui?.takeover !== true) return next();
|
|
8534
|
-
const event = asRecord$
|
|
8524
|
+
const event = asRecord$3(request);
|
|
8535
8525
|
if (event === void 0) return next();
|
|
8536
8526
|
const abortSignal = signal ?? abortSignalOf(event.signal);
|
|
8537
8527
|
if (abortSignal?.aborted === true) return next();
|
|
@@ -8547,7 +8537,7 @@ var TakeoverGate = class {
|
|
|
8547
8537
|
*/
|
|
8548
8538
|
async handleQuestion(request, next, signal) {
|
|
8549
8539
|
if (this.config.ui?.takeover !== true) return next();
|
|
8550
|
-
const event = asRecord$
|
|
8540
|
+
const event = asRecord$3(request);
|
|
8551
8541
|
if (event === void 0) return next();
|
|
8552
8542
|
const abortSignal = signal ?? abortSignalOf(event.signal);
|
|
8553
8543
|
if (abortSignal?.aborted === true) return next();
|
|
@@ -8665,7 +8655,7 @@ var TakeoverGate = class {
|
|
|
8665
8655
|
* dropping an otherwise answerable request.
|
|
8666
8656
|
*/
|
|
8667
8657
|
sessionOf(event) {
|
|
8668
|
-
const agent = asRecord$
|
|
8658
|
+
const agent = asRecord$3(event.agent);
|
|
8669
8659
|
const handle = agent === void 0 ? "" : typeof agent.id === "string" ? agent.id : "";
|
|
8670
8660
|
if (handle.length === 0) return void 0;
|
|
8671
8661
|
try {
|
|
@@ -8675,7 +8665,7 @@ var TakeoverGate = class {
|
|
|
8675
8665
|
}
|
|
8676
8666
|
}
|
|
8677
8667
|
};
|
|
8678
|
-
function asRecord$
|
|
8668
|
+
function asRecord$3(value) {
|
|
8679
8669
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
8680
8670
|
}
|
|
8681
8671
|
function asFunction$1(value) {
|
|
@@ -8683,13 +8673,13 @@ function asFunction$1(value) {
|
|
|
8683
8673
|
}
|
|
8684
8674
|
/** Forward the event signal only when it behaves like one. */
|
|
8685
8675
|
function abortSignalOf(value) {
|
|
8686
|
-
const record = asRecord$
|
|
8676
|
+
const record = asRecord$3(value);
|
|
8687
8677
|
if (record === void 0) return void 0;
|
|
8688
8678
|
if (typeof record.aborted !== "boolean" || typeof record.addEventListener !== "function") return void 0;
|
|
8689
8679
|
return value;
|
|
8690
8680
|
}
|
|
8691
8681
|
function handleOf$1(value) {
|
|
8692
|
-
const record = asRecord$
|
|
8682
|
+
const record = asRecord$3(value);
|
|
8693
8683
|
return typeof record?.handle === "string" && record.handle.length > 0 ? record.handle : void 0;
|
|
8694
8684
|
}
|
|
8695
8685
|
/** Build the JSON-safe approval payload the Client renders. */
|
|
@@ -8703,7 +8693,7 @@ function approvalPayloadOf(event) {
|
|
|
8703
8693
|
};
|
|
8704
8694
|
}
|
|
8705
8695
|
function displayReasonOf(value) {
|
|
8706
|
-
const record = asRecord$
|
|
8696
|
+
const record = asRecord$3(value);
|
|
8707
8697
|
if (record === void 0) return void 0;
|
|
8708
8698
|
const localized = {};
|
|
8709
8699
|
for (const [locale, copy] of Object.entries(record)) if (typeof copy === "string") localized[locale] = copy;
|
|
@@ -8715,7 +8705,7 @@ function questionSpecsOf(value) {
|
|
|
8715
8705
|
const questions = [];
|
|
8716
8706
|
const seen = /* @__PURE__ */ new Set();
|
|
8717
8707
|
for (const candidate of value) {
|
|
8718
|
-
const record = asRecord$
|
|
8708
|
+
const record = asRecord$3(candidate);
|
|
8719
8709
|
if (record === void 0) continue;
|
|
8720
8710
|
const id = typeof record.id === "string" ? record.id : "";
|
|
8721
8711
|
const question = typeof record.question === "string" ? record.question : "";
|
|
@@ -8739,7 +8729,7 @@ function optionsOf(value) {
|
|
|
8739
8729
|
if (!Array.isArray(value)) return [];
|
|
8740
8730
|
const options = [];
|
|
8741
8731
|
for (const candidate of value) {
|
|
8742
|
-
const record = asRecord$
|
|
8732
|
+
const record = asRecord$3(candidate);
|
|
8743
8733
|
if (record === void 0) continue;
|
|
8744
8734
|
const label = typeof record.label === "string" ? record.label : "";
|
|
8745
8735
|
if (label.length === 0) continue;
|
|
@@ -8751,7 +8741,7 @@ function optionsOf(value) {
|
|
|
8751
8741
|
return options;
|
|
8752
8742
|
}
|
|
8753
8743
|
function intentOf$1(value) {
|
|
8754
|
-
const record = asRecord$
|
|
8744
|
+
const record = asRecord$3(value);
|
|
8755
8745
|
if (record === void 0) return void 0;
|
|
8756
8746
|
if (typeof record.kind !== "string" || typeof record.approve !== "string") return void 0;
|
|
8757
8747
|
return {
|
|
@@ -8760,7 +8750,7 @@ function intentOf$1(value) {
|
|
|
8760
8750
|
};
|
|
8761
8751
|
}
|
|
8762
8752
|
function waitOf(value) {
|
|
8763
|
-
const record = asRecord$
|
|
8753
|
+
const record = asRecord$3(value);
|
|
8764
8754
|
if (record === void 0) return void 0;
|
|
8765
8755
|
if (typeof record.callId !== "string" || record.callId.length === 0) return void 0;
|
|
8766
8756
|
return {
|
|
@@ -8769,7 +8759,7 @@ function waitOf(value) {
|
|
|
8769
8759
|
};
|
|
8770
8760
|
}
|
|
8771
8761
|
function validateAnswer(entry, answer) {
|
|
8772
|
-
const record = asRecord$
|
|
8762
|
+
const record = asRecord$3(answer);
|
|
8773
8763
|
if (record === void 0) throw new InvalidArgumentError("takeover answer must be an object");
|
|
8774
8764
|
if (record.kind !== entry.type) throw new InvalidArgumentError(`takeover answer kind must be "${entry.type}"`, { kind: record.kind });
|
|
8775
8765
|
if (entry.type === "approval") return {
|
|
@@ -8794,7 +8784,7 @@ function questionAnswersOf(questions, value) {
|
|
|
8794
8784
|
const seen = /* @__PURE__ */ new Set();
|
|
8795
8785
|
const answers = [];
|
|
8796
8786
|
for (const candidate of value) {
|
|
8797
|
-
const record = asRecord$
|
|
8787
|
+
const record = asRecord$3(candidate);
|
|
8798
8788
|
if (record === void 0) throw new InvalidArgumentError("question answer entry must be an object");
|
|
8799
8789
|
const id = record.id;
|
|
8800
8790
|
if (typeof id !== "string" || id.length === 0) throw new InvalidArgumentError("question answer entry must carry a non-empty id");
|
|
@@ -12802,9 +12792,6 @@ var TurnPipeline = class {
|
|
|
12802
12792
|
});
|
|
12803
12793
|
return report;
|
|
12804
12794
|
}
|
|
12805
|
-
if (turn.effort === profile.reasoningEffort) policy.applied.push("effort");
|
|
12806
|
-
else this.defer(policy, report, "reasoningEffort", `turn effort "${turn.effort}" differs from the scene effort "${profile.reasoningEffort}"`);
|
|
12807
|
-
this.defer(policy, report, "thinking", "no DSH model-config port: the pipeline records the scene but cannot toggle reasoning");
|
|
12808
12795
|
this.defer(policy, report, "allowBash", "no execution-policy port: bash permission is left to the DSH permission preset");
|
|
12809
12796
|
this.defer(policy, report, "maxIteration", "no iterative driver consumes scene.maxIteration yet");
|
|
12810
12797
|
policy.applied.push("allowWeb");
|
|
@@ -12973,6 +12960,12 @@ function evidenceCount(value) {
|
|
|
12973
12960
|
return 1;
|
|
12974
12961
|
}
|
|
12975
12962
|
}
|
|
12963
|
+
/** The neutral effort recorded when DSH has not committed a route yet. */
|
|
12964
|
+
const DefaultEffort = "medium";
|
|
12965
|
+
/** Narrow an unknown to a plain record without throwing. */
|
|
12966
|
+
function recordOf$1(value) {
|
|
12967
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
12968
|
+
}
|
|
12976
12969
|
function textOr(value, fallback) {
|
|
12977
12970
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
|
|
12978
12971
|
}
|
|
@@ -13137,6 +13130,26 @@ var CoordinationService = class extends Service {
|
|
|
13137
13130
|
listProfessionRecord(session) {
|
|
13138
13131
|
return this.profession.listProfessionRecord(session);
|
|
13139
13132
|
}
|
|
13133
|
+
/**
|
|
13134
|
+
* The reasoning effort DSH will send for this session.
|
|
13135
|
+
*
|
|
13136
|
+
* Read from the session's own request header instead of plugin config: the
|
|
13137
|
+
* model page owns this choice and the plugin never overrides it. A session
|
|
13138
|
+
* with no committed route yet has no header, so the row records the neutral
|
|
13139
|
+
* default rather than guessing from a scene literal.
|
|
13140
|
+
*/
|
|
13141
|
+
observedEffort(sessionId) {
|
|
13142
|
+
try {
|
|
13143
|
+
const raw = recordOf$1(this.runtime.rawSession(sessionId));
|
|
13144
|
+
const read = raw?.requestHeader;
|
|
13145
|
+
if (typeof read !== "function") return DefaultEffort;
|
|
13146
|
+
const config = recordOf$1(recordOf$1(read.call(raw))?.config);
|
|
13147
|
+
const effort = typeof config?.reasoningEffort === "string" ? config.reasoningEffort.trim() : "";
|
|
13148
|
+
return effort.length > 0 ? effort : DefaultEffort;
|
|
13149
|
+
} catch {
|
|
13150
|
+
return DefaultEffort;
|
|
13151
|
+
}
|
|
13152
|
+
}
|
|
13140
13153
|
/** Open (or reuse) the coordination turn for one DSH turn. */
|
|
13141
13154
|
async beginTurn(sessionId, dshTurn, promptText = "") {
|
|
13142
13155
|
const session = this.session.ensure(sessionId);
|
|
@@ -13144,7 +13157,6 @@ var CoordinationService = class extends Service {
|
|
|
13144
13157
|
if (existing !== void 0 && existing.dshTurn === dshTurn) return this.turn.get(existing.identity);
|
|
13145
13158
|
if (existing !== void 0) this.finishQuietly(existing.identity, "done");
|
|
13146
13159
|
const selection = this.selection.get(sessionId);
|
|
13147
|
-
const scene = resolveSceneProfile(this.config, selection.scene);
|
|
13148
13160
|
const turn = this.turn.create(session.identity, {
|
|
13149
13161
|
content: promptText,
|
|
13150
13162
|
profession: selection.profession,
|
|
@@ -13152,7 +13164,7 @@ var CoordinationService = class extends Service {
|
|
|
13152
13164
|
scene: selection.scene,
|
|
13153
13165
|
validation: selection.validation,
|
|
13154
13166
|
command: "none",
|
|
13155
|
-
effort:
|
|
13167
|
+
effort: this.observedEffort(sessionId)
|
|
13156
13168
|
});
|
|
13157
13169
|
this.dshTurns.set(sessionId, {
|
|
13158
13170
|
dshTurn,
|
|
@@ -13436,28 +13448,32 @@ function bootHost(db, config, handles, clock) {
|
|
|
13436
13448
|
function isRecord(value) {
|
|
13437
13449
|
return typeof value === "object" && value !== null;
|
|
13438
13450
|
}
|
|
13439
|
-
function asRecord$
|
|
13451
|
+
function asRecord$2(value) {
|
|
13440
13452
|
return isRecord(value) ? value : void 0;
|
|
13441
13453
|
}
|
|
13442
13454
|
function asFunction(value) {
|
|
13443
13455
|
return typeof value === "function" ? value : void 0;
|
|
13444
13456
|
}
|
|
13445
|
-
function asString(value, fallback = "") {
|
|
13457
|
+
function asString$1(value, fallback = "") {
|
|
13446
13458
|
return typeof value === "string" ? value : fallback;
|
|
13447
13459
|
}
|
|
13448
13460
|
/** Resolve one Cordis service without throwing when it is absent. */
|
|
13449
13461
|
function serviceOf(context, name) {
|
|
13450
|
-
const holder = asRecord$
|
|
13462
|
+
const holder = asRecord$2(context);
|
|
13451
13463
|
if (holder === void 0) return void 0;
|
|
13452
13464
|
const get = asFunction(holder.get);
|
|
13453
13465
|
if (get !== void 0) try {
|
|
13454
13466
|
const value = get.call(context, name);
|
|
13455
13467
|
if (isRecord(value) || typeof value === "function") return isRecord(value) ? value : { call: value };
|
|
13456
13468
|
} catch {}
|
|
13457
|
-
|
|
13469
|
+
try {
|
|
13470
|
+
return asRecord$2(holder[name]);
|
|
13471
|
+
} catch {
|
|
13472
|
+
return;
|
|
13473
|
+
}
|
|
13458
13474
|
}
|
|
13459
13475
|
function makeLogger(context) {
|
|
13460
|
-
const logger = asRecord$
|
|
13476
|
+
const logger = asRecord$2(context)?.["logger"];
|
|
13461
13477
|
if (!isRecord(logger)) return void 0;
|
|
13462
13478
|
const record = (level) => {
|
|
13463
13479
|
const fn = asFunction(logger[level]);
|
|
@@ -13476,42 +13492,42 @@ function makeLogger(context) {
|
|
|
13476
13492
|
};
|
|
13477
13493
|
}
|
|
13478
13494
|
function adaptProvider(value) {
|
|
13479
|
-
const record = asRecord$
|
|
13480
|
-
const id = asString(record.id) || asString(record.provider) || asString(record.name);
|
|
13495
|
+
const record = asRecord$2(value) ?? {};
|
|
13496
|
+
const id = asString$1(record.id) || asString$1(record.provider) || asString$1(record.name);
|
|
13481
13497
|
return {
|
|
13482
13498
|
id,
|
|
13483
|
-
name: asString(record.name) || id
|
|
13499
|
+
name: asString$1(record.name) || id
|
|
13484
13500
|
};
|
|
13485
13501
|
}
|
|
13486
13502
|
function adaptConfigurableProvider(value) {
|
|
13487
|
-
const record = asRecord$
|
|
13488
|
-
const provider = asString(record.provider) || asString(record.id);
|
|
13503
|
+
const record = asRecord$2(value) ?? {};
|
|
13504
|
+
const provider = asString$1(record.provider) || asString$1(record.id);
|
|
13489
13505
|
const settingsPath = Array.isArray(record.settingsPath) ? record.settingsPath.filter((entry) => typeof entry === "string") : [];
|
|
13490
13506
|
return {
|
|
13491
13507
|
provider,
|
|
13492
|
-
displayName: asString(record.displayName) || provider,
|
|
13493
|
-
settingsNs: asString(record.settingsNs),
|
|
13508
|
+
displayName: asString$1(record.displayName) || provider,
|
|
13509
|
+
settingsNs: asString$1(record.settingsNs),
|
|
13494
13510
|
settingsPath,
|
|
13495
13511
|
...typeof record.declared === "boolean" ? { declared: record.declared } : {},
|
|
13496
13512
|
...typeof record.error === "string" ? { error: record.error } : {}
|
|
13497
13513
|
};
|
|
13498
13514
|
}
|
|
13499
13515
|
function adaptModel(provider, value) {
|
|
13500
|
-
const record = asRecord$
|
|
13501
|
-
const id = asString(record.id) || asString(record.model);
|
|
13516
|
+
const record = asRecord$2(value) ?? {};
|
|
13517
|
+
const id = asString$1(record.id) || asString$1(record.model);
|
|
13502
13518
|
return {
|
|
13503
|
-
provider: asString(record.provider) || provider,
|
|
13519
|
+
provider: asString$1(record.provider) || provider,
|
|
13504
13520
|
id,
|
|
13505
|
-
name: asString(record.name) || id,
|
|
13521
|
+
name: asString$1(record.name) || id,
|
|
13506
13522
|
...typeof record.description === "string" ? { description: record.description } : {},
|
|
13507
13523
|
...Array.isArray(record.inputModalities) ? { inputModalities: record.inputModalities.filter((entry) => typeof entry === "string") } : {}
|
|
13508
13524
|
};
|
|
13509
13525
|
}
|
|
13510
13526
|
function adaptResolvedModel(provider, model, value) {
|
|
13511
13527
|
const base = adaptModel(provider, value);
|
|
13512
|
-
const record = asRecord$
|
|
13513
|
-
const context = asRecord$
|
|
13514
|
-
const reasoning = asRecord$
|
|
13528
|
+
const record = asRecord$2(value) ?? {};
|
|
13529
|
+
const context = asRecord$2(record.context);
|
|
13530
|
+
const reasoning = asRecord$2(record.reasoning);
|
|
13515
13531
|
const efforts = Array.isArray(reasoning?.efforts) ? reasoning.efforts : [];
|
|
13516
13532
|
return {
|
|
13517
13533
|
...base,
|
|
@@ -13519,9 +13535,9 @@ function adaptResolvedModel(provider, model, value) {
|
|
|
13519
13535
|
...context !== void 0 && typeof context.contextWindow === "number" ? { context: { contextWindow: context.contextWindow } } : {},
|
|
13520
13536
|
...typeof record.defaultMaxTokens === "number" ? { defaultMaxTokens: record.defaultMaxTokens } : {},
|
|
13521
13537
|
...reasoning === void 0 ? {} : { reasoning: {
|
|
13522
|
-
efforts: efforts.map((entry) => asRecord$
|
|
13523
|
-
id: asString(entry.id),
|
|
13524
|
-
name: asString(entry.name) || asString(entry.id),
|
|
13538
|
+
efforts: efforts.map((entry) => asRecord$2(entry)).filter((entry) => entry !== void 0).map((entry) => ({
|
|
13539
|
+
id: asString$1(entry.id),
|
|
13540
|
+
name: asString$1(entry.name) || asString$1(entry.id),
|
|
13525
13541
|
...typeof entry.description === "string" ? { description: entry.description } : {}
|
|
13526
13542
|
})),
|
|
13527
13543
|
...typeof reasoning.defaultEffort === "string" ? { defaultEffort: reasoning.defaultEffort } : {}
|
|
@@ -13565,9 +13581,9 @@ function makeAgentPort(context) {
|
|
|
13565
13581
|
const list = asFunction(agents.list);
|
|
13566
13582
|
if (get === void 0 && list === void 0) return void 0;
|
|
13567
13583
|
const toView = (value) => {
|
|
13568
|
-
const record = asRecord$
|
|
13584
|
+
const record = asRecord$2(value);
|
|
13569
13585
|
if (record === void 0) return void 0;
|
|
13570
|
-
const id = asString(record.id);
|
|
13586
|
+
const id = asString$1(record.id);
|
|
13571
13587
|
return id.length === 0 ? void 0 : {
|
|
13572
13588
|
id,
|
|
13573
13589
|
session: record.session
|
|
@@ -13677,7 +13693,7 @@ function makeCompactionPort(context, rawAgent) {
|
|
|
13677
13693
|
return await compactNow.call(compaction, live, signal);
|
|
13678
13694
|
},
|
|
13679
13695
|
...compactIfNeeded === void 0 ? {} : { compactIfNeeded: async (agent, trigger, signal) => {
|
|
13680
|
-
const live = asRecord$
|
|
13696
|
+
const live = asRecord$2(rawAgent(agent.id)) ?? {};
|
|
13681
13697
|
const session = live.session;
|
|
13682
13698
|
if (session === void 0) return null;
|
|
13683
13699
|
const options = isRecord(live.options) ? live.options : {};
|
|
@@ -13715,22 +13731,22 @@ function makePermissionPresetPort(context) {
|
|
|
13715
13731
|
const resolve = asFunction(presets.resolve);
|
|
13716
13732
|
return {
|
|
13717
13733
|
catalog: () => {
|
|
13718
|
-
const value = asRecord$
|
|
13734
|
+
const value = asRecord$2(catalog.call(presets)) ?? {};
|
|
13719
13735
|
return {
|
|
13720
|
-
options: (Array.isArray(value.options) ? value.options : []).map((entry) => asRecord$
|
|
13721
|
-
value: asString(entry.value),
|
|
13722
|
-
name: asString(entry.name) || asString(entry.value),
|
|
13736
|
+
options: (Array.isArray(value.options) ? value.options : []).map((entry) => asRecord$2(entry)).filter((entry) => entry !== void 0).map((entry) => ({
|
|
13737
|
+
value: asString$1(entry.value),
|
|
13738
|
+
name: asString$1(entry.name) || asString$1(entry.value),
|
|
13723
13739
|
...typeof entry.description === "string" ? { description: entry.description } : {}
|
|
13724
13740
|
})).filter((entry) => entry.value.length > 0),
|
|
13725
13741
|
...typeof value.defaultPreset === "string" ? { defaultPreset: value.defaultPreset } : {}
|
|
13726
13742
|
};
|
|
13727
13743
|
},
|
|
13728
|
-
current: (session) => asString(current.call(presets, session)),
|
|
13744
|
+
current: (session) => asString$1(current.call(presets, session)),
|
|
13729
13745
|
set: (session, name) => {
|
|
13730
13746
|
set.call(presets, session, name);
|
|
13731
13747
|
},
|
|
13732
13748
|
...resolve === void 0 ? {} : { resolve: (name) => {
|
|
13733
|
-
const value = asRecord$
|
|
13749
|
+
const value = asRecord$2(resolve.call(presets, name)) ?? {};
|
|
13734
13750
|
return {
|
|
13735
13751
|
...typeof value.sandbox === "string" ? { sandbox: value.sandbox } : {},
|
|
13736
13752
|
...typeof value.approval === "string" ? { approval: value.approval } : {},
|
|
@@ -13745,15 +13761,15 @@ function makeSandboxPolicyPort(context) {
|
|
|
13745
13761
|
if (policy === void 0) return void 0;
|
|
13746
13762
|
const resolve = asFunction(policy.resolve);
|
|
13747
13763
|
const overrideOf = asFunction(policy.overrideOf);
|
|
13748
|
-
const defaultMode = asString(policy.defaultMode, "workspace-write");
|
|
13764
|
+
const defaultMode = asString$1(policy.defaultMode, "workspace-write");
|
|
13749
13765
|
if (resolve === void 0) return void 0;
|
|
13750
13766
|
return {
|
|
13751
13767
|
defaultMode,
|
|
13752
13768
|
resolve: (request) => {
|
|
13753
|
-
const value = asRecord$
|
|
13769
|
+
const value = asRecord$2(resolve.call(policy, request ?? {})) ?? {};
|
|
13754
13770
|
return {
|
|
13755
|
-
mode: asString(value.mode, defaultMode),
|
|
13756
|
-
workspaceRoot: asString(value.workspaceRoot)
|
|
13771
|
+
mode: asString$1(value.mode, defaultMode),
|
|
13772
|
+
workspaceRoot: asString$1(value.workspaceRoot)
|
|
13757
13773
|
};
|
|
13758
13774
|
},
|
|
13759
13775
|
overrideOf: (session) => {
|
|
@@ -13784,7 +13800,7 @@ function createRuntime(context) {
|
|
|
13784
13800
|
};
|
|
13785
13801
|
}
|
|
13786
13802
|
function warn(context, message, error) {
|
|
13787
|
-
const logger = asRecord$
|
|
13803
|
+
const logger = asRecord$2(asRecord$2(context)?.["logger"]);
|
|
13788
13804
|
const fn = asFunction(logger?.warn);
|
|
13789
13805
|
if (fn !== void 0) try {
|
|
13790
13806
|
fn.call(logger, `deepseek-coordination: ${message}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -13801,7 +13817,7 @@ function contentText(value) {
|
|
|
13801
13817
|
parts.push(entry);
|
|
13802
13818
|
continue;
|
|
13803
13819
|
}
|
|
13804
|
-
const text = asString(asRecord$
|
|
13820
|
+
const text = asString$1(asRecord$2(entry)?.text);
|
|
13805
13821
|
if (text.length > 0) parts.push(text);
|
|
13806
13822
|
}
|
|
13807
13823
|
return parts.join("\n");
|
|
@@ -13814,7 +13830,7 @@ function contentText(value) {
|
|
|
13814
13830
|
function promptTextOfMessages(messages) {
|
|
13815
13831
|
if (!Array.isArray(messages)) return void 0;
|
|
13816
13832
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
13817
|
-
const message = asRecord$
|
|
13833
|
+
const message = asRecord$2(messages[index]);
|
|
13818
13834
|
if (message?.role !== "user") continue;
|
|
13819
13835
|
const text = contentText(message.content).trim();
|
|
13820
13836
|
if (text.length > 0) return text;
|
|
@@ -13832,12 +13848,12 @@ const TurnHookBudgetMs = 5e3;
|
|
|
13832
13848
|
* here at all.
|
|
13833
13849
|
*/
|
|
13834
13850
|
function installTurnLifecycle(context, lifecycle) {
|
|
13835
|
-
const on = asFunction(asRecord$
|
|
13851
|
+
const on = asFunction(asRecord$2(context)?.on);
|
|
13836
13852
|
if (on === void 0) return () => {};
|
|
13837
13853
|
const disposers = [];
|
|
13838
13854
|
const preStep = on.call(context, "agent/pre-step", async (payload, next) => {
|
|
13839
|
-
const record = asRecord$
|
|
13840
|
-
const agentId = asString(asRecord$
|
|
13855
|
+
const record = asRecord$2(payload);
|
|
13856
|
+
const agentId = asString$1(asRecord$2(record?.agent)?.id);
|
|
13841
13857
|
if (agentId.length > 0 && typeof record?.turn === "number") {
|
|
13842
13858
|
const prompt = promptTextOfMessages(record?.messages);
|
|
13843
13859
|
const observation = {
|
|
@@ -13864,8 +13880,8 @@ function installTurnLifecycle(context, lifecycle) {
|
|
|
13864
13880
|
});
|
|
13865
13881
|
if (typeof preStep === "function") disposers.push(preStep);
|
|
13866
13882
|
const stopping = on.call(context, "agent/turn-stopping", async (payload) => {
|
|
13867
|
-
const record = asRecord$
|
|
13868
|
-
const agentId = asString(asRecord$
|
|
13883
|
+
const record = asRecord$2(payload);
|
|
13884
|
+
const agentId = asString$1(asRecord$2(record?.agent)?.id);
|
|
13869
13885
|
try {
|
|
13870
13886
|
if (agentId.length > 0 && typeof record?.turn === "number") await lifecycle.end({
|
|
13871
13887
|
session: agentId,
|
|
@@ -13878,8 +13894,8 @@ function installTurnLifecycle(context, lifecycle) {
|
|
|
13878
13894
|
if (typeof stopping === "function") disposers.push(stopping);
|
|
13879
13895
|
if (lifecycle.fail !== void 0) {
|
|
13880
13896
|
const failed = on.call(context, "agent/error", (payload) => {
|
|
13881
|
-
const record = asRecord$
|
|
13882
|
-
const agentId = asString(asRecord$
|
|
13897
|
+
const record = asRecord$2(payload);
|
|
13898
|
+
const agentId = asString$1(asRecord$2(record?.agent)?.id);
|
|
13883
13899
|
if (agentId.length === 0 || typeof record?.turn !== "number") return;
|
|
13884
13900
|
const turn = record.turn;
|
|
13885
13901
|
const failure = record.error;
|
|
@@ -13900,6 +13916,463 @@ function installTurnLifecycle(context, lifecycle) {
|
|
|
13900
13916
|
};
|
|
13901
13917
|
}
|
|
13902
13918
|
//#endregion
|
|
13919
|
+
//#region src/host/settings-store.ts
|
|
13920
|
+
/**
|
|
13921
|
+
* Durable settings edits for the host's own knobs (v24 §19).
|
|
13922
|
+
*
|
|
13923
|
+
* DSH's settings service only persists fields whose schema node is volatile,
|
|
13924
|
+
* and a volatile node changes every parsed value into a reference wrapper —
|
|
13925
|
+
* which this host cannot adopt without rewriting every config reader. The
|
|
13926
|
+
* settings page therefore persists through this module instead: one small JSON
|
|
13927
|
+
* document beside the coordination database, holding only the paths the page
|
|
13928
|
+
* actually wrote.
|
|
13929
|
+
*
|
|
13930
|
+
* Contract:
|
|
13931
|
+
* - the document is a flat `{ "<dotted.path>": scalar }` map, the same shape
|
|
13932
|
+
* the `POST /config` route speaks, so nothing is nested twice;
|
|
13933
|
+
* - a missing, unreadable or malformed file means "no overrides", never an
|
|
13934
|
+
* error: a deployment that never opened the page has no state to load;
|
|
13935
|
+
* - writes are atomic enough for one process (temp file + rename), so a crash
|
|
13936
|
+
* mid-write cannot truncate the previous document;
|
|
13937
|
+
* - the two deployment secrets are never stored: `applyConfigPatch` refuses
|
|
13938
|
+
* them before this module is reached, and the filter is repeated here so a
|
|
13939
|
+
* hand-edited file cannot smuggle them in.
|
|
13940
|
+
*/
|
|
13941
|
+
/** File name placed beside the coordination database. */
|
|
13942
|
+
const SETTINGS_FILE = "config.overrides.json";
|
|
13943
|
+
/** Paths this module must never persist, even when a patch names them. */
|
|
13944
|
+
const REFUSED_KEYS = /* @__PURE__ */ new Set(["database", "databasePath"]);
|
|
13945
|
+
/** The override document beside one database file. */
|
|
13946
|
+
function settingsPath(databasePath) {
|
|
13947
|
+
return join(dirname(databasePath), SETTINGS_FILE);
|
|
13948
|
+
}
|
|
13949
|
+
/**
|
|
13950
|
+
* Read the stored overrides of one profile.
|
|
13951
|
+
*
|
|
13952
|
+
* Non-scalar entries and refused keys are dropped rather than surfaced: a
|
|
13953
|
+
* corrupt document must degrade to "no overrides", so the resolved config stays
|
|
13954
|
+
* exactly what the deployment patch describes.
|
|
13955
|
+
*/
|
|
13956
|
+
function readOverrides(file) {
|
|
13957
|
+
let text;
|
|
13958
|
+
try {
|
|
13959
|
+
text = readFileSync(file, "utf8");
|
|
13960
|
+
} catch {
|
|
13961
|
+
return {};
|
|
13962
|
+
}
|
|
13963
|
+
let parsed;
|
|
13964
|
+
try {
|
|
13965
|
+
parsed = JSON.parse(text);
|
|
13966
|
+
} catch {
|
|
13967
|
+
return {};
|
|
13968
|
+
}
|
|
13969
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
13970
|
+
const overrides = {};
|
|
13971
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
13972
|
+
if (REFUSED_KEYS.has(key)) continue;
|
|
13973
|
+
if (typeof value === "string" || typeof value === "boolean") overrides[key] = value;
|
|
13974
|
+
else if (typeof value === "number" && Number.isFinite(value)) overrides[key] = value;
|
|
13975
|
+
}
|
|
13976
|
+
return overrides;
|
|
13977
|
+
}
|
|
13978
|
+
/**
|
|
13979
|
+
* Merge one applied patch into the stored overrides.
|
|
13980
|
+
*
|
|
13981
|
+
* @returns true when the document now holds the patch; false when the directory
|
|
13982
|
+
* or file could not be written (the caller then reports `persisted: false`).
|
|
13983
|
+
*/
|
|
13984
|
+
function writeOverrides(file, patch) {
|
|
13985
|
+
const merged = readOverrides(file);
|
|
13986
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
13987
|
+
if (REFUSED_KEYS.has(key)) continue;
|
|
13988
|
+
merged[key] = value;
|
|
13989
|
+
}
|
|
13990
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
13991
|
+
try {
|
|
13992
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
13993
|
+
writeFileSync(temporary, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
13994
|
+
renameSync(temporary, file);
|
|
13995
|
+
return true;
|
|
13996
|
+
} catch {
|
|
13997
|
+
return false;
|
|
13998
|
+
}
|
|
13999
|
+
}
|
|
14000
|
+
//#endregion
|
|
14001
|
+
//#region src/host/proxy-adapter.ts
|
|
14002
|
+
/**
|
|
14003
|
+
* Provider allowlist shared by the adapter and the routing listener.
|
|
14004
|
+
*
|
|
14005
|
+
* Only the deployment's own model routes may be proxied. Anything else (a
|
|
14006
|
+
* provider another plugin registered, a future third-party route) is left
|
|
14007
|
+
* exactly as the harness built it.
|
|
14008
|
+
*/
|
|
14009
|
+
const PROXY_ALLOWED_PROVIDERS = [
|
|
14010
|
+
"deepseek",
|
|
14011
|
+
"deepseek-account",
|
|
14012
|
+
"deepseek-official"
|
|
14013
|
+
];
|
|
14014
|
+
/** True when the proxy may intercept this provider route. */
|
|
14015
|
+
function isProxyProviderAllowed(provider, allow = PROXY_ALLOWED_PROVIDERS) {
|
|
14016
|
+
return typeof provider === "string" && allow.includes(provider);
|
|
14017
|
+
}
|
|
14018
|
+
function asRecord$1(value) {
|
|
14019
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
14020
|
+
}
|
|
14021
|
+
function asString(value) {
|
|
14022
|
+
return typeof value === "string" ? value : "";
|
|
14023
|
+
}
|
|
14024
|
+
/** Structural shape of one request: counts only, never content. */
|
|
14025
|
+
function summarise(request) {
|
|
14026
|
+
const messages = Array.isArray(request.messages) ? request.messages : [];
|
|
14027
|
+
let chars = 0;
|
|
14028
|
+
for (const message of messages) {
|
|
14029
|
+
const content = asRecord$1(message)?.content;
|
|
14030
|
+
if (typeof content === "string") chars += content.length;
|
|
14031
|
+
else if (Array.isArray(content)) for (const block of content) {
|
|
14032
|
+
const record = asRecord$1(block);
|
|
14033
|
+
if (typeof record?.text === "string") chars += record.text.length;
|
|
14034
|
+
}
|
|
14035
|
+
}
|
|
14036
|
+
const tools = Array.isArray(request.tools) ? request.tools.length : void 0;
|
|
14037
|
+
return {
|
|
14038
|
+
messages: messages.length,
|
|
14039
|
+
chars,
|
|
14040
|
+
...tools === void 0 ? {} : { tools }
|
|
14041
|
+
};
|
|
14042
|
+
}
|
|
14043
|
+
/** Build the adapter. Registration and disposal belong to the caller. */
|
|
14044
|
+
function createProxyAdapter(options) {
|
|
14045
|
+
const { provider: proxyProvider, llm } = options;
|
|
14046
|
+
const allow = options.allowProviders ?? PROXY_ALLOWED_PROVIDERS;
|
|
14047
|
+
/** The real route, or `undefined` before the first request names one. */
|
|
14048
|
+
const upstream = () => {
|
|
14049
|
+
const route = options.routeOf();
|
|
14050
|
+
if (route === void 0) return void 0;
|
|
14051
|
+
const provider = asString(route.provider).trim();
|
|
14052
|
+
if (provider.length === 0) return void 0;
|
|
14053
|
+
const model = asString(route.model).trim();
|
|
14054
|
+
return {
|
|
14055
|
+
provider,
|
|
14056
|
+
...model.length === 0 ? {} : { model }
|
|
14057
|
+
};
|
|
14058
|
+
};
|
|
14059
|
+
const stream = (request) => {
|
|
14060
|
+
const frozen = asRecord$1(request);
|
|
14061
|
+
if (frozen === void 0) {
|
|
14062
|
+
options.trace?.({
|
|
14063
|
+
event: "refuse",
|
|
14064
|
+
provider: proxyProvider,
|
|
14065
|
+
reason: "request-not-an-object"
|
|
14066
|
+
});
|
|
14067
|
+
throw new Error("coordination proxy: the request is not an object");
|
|
14068
|
+
}
|
|
14069
|
+
const route = upstream();
|
|
14070
|
+
if (route === void 0) {
|
|
14071
|
+
options.trace?.({
|
|
14072
|
+
event: "refuse",
|
|
14073
|
+
provider: proxyProvider,
|
|
14074
|
+
reason: "no-upstream-route"
|
|
14075
|
+
});
|
|
14076
|
+
throw new Error("coordination proxy: no upstream route recorded yet");
|
|
14077
|
+
}
|
|
14078
|
+
if (route.provider === proxyProvider) {
|
|
14079
|
+
options.trace?.({
|
|
14080
|
+
event: "refuse",
|
|
14081
|
+
provider: proxyProvider,
|
|
14082
|
+
reason: "self-forward"
|
|
14083
|
+
});
|
|
14084
|
+
throw new Error(`coordination proxy: refusing to forward "${proxyProvider}" to itself`);
|
|
14085
|
+
}
|
|
14086
|
+
if (!isProxyProviderAllowed(route.provider, allow)) {
|
|
14087
|
+
options.trace?.({
|
|
14088
|
+
event: "refuse",
|
|
14089
|
+
provider: route.provider,
|
|
14090
|
+
reason: "provider-not-allowlisted"
|
|
14091
|
+
});
|
|
14092
|
+
throw new Error(`coordination proxy: provider "${route.provider}" is not on the allowlist`);
|
|
14093
|
+
}
|
|
14094
|
+
const forwarding = llm.stream;
|
|
14095
|
+
if (typeof forwarding !== "function") {
|
|
14096
|
+
options.trace?.({
|
|
14097
|
+
event: "refuse",
|
|
14098
|
+
provider: route.provider,
|
|
14099
|
+
reason: "stream-unavailable"
|
|
14100
|
+
});
|
|
14101
|
+
throw new Error("coordination proxy: ctx.llm.stream is unavailable");
|
|
14102
|
+
}
|
|
14103
|
+
const outgoing = {
|
|
14104
|
+
...options.decorate === void 0 ? { ...frozen } : options.decorate(frozen),
|
|
14105
|
+
provider: route.provider
|
|
14106
|
+
};
|
|
14107
|
+
options.trace?.({
|
|
14108
|
+
event: "forward",
|
|
14109
|
+
provider: route.provider,
|
|
14110
|
+
...typeof outgoing.model === "string" ? { model: outgoing.model } : {},
|
|
14111
|
+
...summarise(outgoing)
|
|
14112
|
+
});
|
|
14113
|
+
return forwarding.call(llm, outgoing);
|
|
14114
|
+
};
|
|
14115
|
+
return {
|
|
14116
|
+
providerInfo: (provider) => ({
|
|
14117
|
+
id: provider,
|
|
14118
|
+
name: "Coordination Proxy"
|
|
14119
|
+
}),
|
|
14120
|
+
providerRetryPolicy: () => void 0,
|
|
14121
|
+
imageRequestPricing: () => void 0,
|
|
14122
|
+
imageRequestTarget: (_provider, _model, image) => ({
|
|
14123
|
+
width: image.width,
|
|
14124
|
+
height: image.height,
|
|
14125
|
+
maxBytes: image.bytes
|
|
14126
|
+
}),
|
|
14127
|
+
async listModels(provider) {
|
|
14128
|
+
const route = upstream();
|
|
14129
|
+
if (route === void 0 || typeof llm.listModels !== "function") return [];
|
|
14130
|
+
try {
|
|
14131
|
+
const models = await llm.listModels(route.provider);
|
|
14132
|
+
return Array.isArray(models) ? models.map((entry) => {
|
|
14133
|
+
const record = asRecord$1(entry);
|
|
14134
|
+
return record === void 0 ? entry : {
|
|
14135
|
+
...record,
|
|
14136
|
+
provider
|
|
14137
|
+
};
|
|
14138
|
+
}) : [];
|
|
14139
|
+
} catch {
|
|
14140
|
+
return [];
|
|
14141
|
+
}
|
|
14142
|
+
},
|
|
14143
|
+
async resolveModel(provider, model, signal) {
|
|
14144
|
+
const route = upstream();
|
|
14145
|
+
if (route !== void 0 && typeof llm.resolveModelInfo === "function") try {
|
|
14146
|
+
const info = asRecord$1(await llm.resolveModelInfo(route.provider, model, signal));
|
|
14147
|
+
if (info !== void 0) return {
|
|
14148
|
+
...info,
|
|
14149
|
+
provider,
|
|
14150
|
+
id: model,
|
|
14151
|
+
name: asString(info.name) || model
|
|
14152
|
+
};
|
|
14153
|
+
} catch {}
|
|
14154
|
+
return {
|
|
14155
|
+
provider,
|
|
14156
|
+
id: model,
|
|
14157
|
+
name: model
|
|
14158
|
+
};
|
|
14159
|
+
},
|
|
14160
|
+
async prepareCall(provider, model, signal) {
|
|
14161
|
+
return {
|
|
14162
|
+
model: await this.resolveModel(provider, model, signal),
|
|
14163
|
+
stream: (request) => stream(request)
|
|
14164
|
+
};
|
|
14165
|
+
},
|
|
14166
|
+
stream
|
|
14167
|
+
};
|
|
14168
|
+
}
|
|
14169
|
+
//#endregion
|
|
14170
|
+
//#region src/host/model-brand.ts
|
|
14171
|
+
/**
|
|
14172
|
+
* Built-in profiles.
|
|
14173
|
+
*
|
|
14174
|
+
* Where a provider documents both an automatic and an explicit mode (Google),
|
|
14175
|
+
* the conservative automatic reading is used: the plugin can keep a prefix
|
|
14176
|
+
* stable, and cannot emit markers.
|
|
14177
|
+
*/
|
|
14178
|
+
const BRAND_PROFILES = {
|
|
14179
|
+
deepseek: {
|
|
14180
|
+
brand: "deepseek",
|
|
14181
|
+
label: "DeepSeek",
|
|
14182
|
+
automaticPrefixCache: true,
|
|
14183
|
+
explicitCacheBreakpoints: false,
|
|
14184
|
+
minPrefixTokens: 64,
|
|
14185
|
+
effortBreaksCache: true,
|
|
14186
|
+
stripVolatileHelps: true,
|
|
14187
|
+
note: "Automatic prefix cache; keep the prefix stable and avoid effort changes."
|
|
14188
|
+
},
|
|
14189
|
+
openai: {
|
|
14190
|
+
brand: "openai",
|
|
14191
|
+
label: "OpenAI",
|
|
14192
|
+
automaticPrefixCache: true,
|
|
14193
|
+
explicitCacheBreakpoints: false,
|
|
14194
|
+
minPrefixTokens: 1024,
|
|
14195
|
+
effortBreaksCache: false,
|
|
14196
|
+
stripVolatileHelps: true,
|
|
14197
|
+
note: "Automatic prompt cache above ~1k tokens; effort does not change the prefix."
|
|
14198
|
+
},
|
|
14199
|
+
anthropic: {
|
|
14200
|
+
brand: "anthropic",
|
|
14201
|
+
label: "Anthropic",
|
|
14202
|
+
automaticPrefixCache: false,
|
|
14203
|
+
explicitCacheBreakpoints: true,
|
|
14204
|
+
minPrefixTokens: 1024,
|
|
14205
|
+
effortBreaksCache: true,
|
|
14206
|
+
stripVolatileHelps: false,
|
|
14207
|
+
note: "Explicit cache_control breakpoints only; this plugin cannot emit them."
|
|
14208
|
+
},
|
|
14209
|
+
google: {
|
|
14210
|
+
brand: "google",
|
|
14211
|
+
label: "Google",
|
|
14212
|
+
automaticPrefixCache: true,
|
|
14213
|
+
explicitCacheBreakpoints: true,
|
|
14214
|
+
minPrefixTokens: 1024,
|
|
14215
|
+
effortBreaksCache: false,
|
|
14216
|
+
stripVolatileHelps: true,
|
|
14217
|
+
note: "Implicit prefix cache plus optional explicit contexts; the implicit mode is the safe lever."
|
|
14218
|
+
},
|
|
14219
|
+
xai: {
|
|
14220
|
+
brand: "xai",
|
|
14221
|
+
label: "xAI",
|
|
14222
|
+
automaticPrefixCache: true,
|
|
14223
|
+
explicitCacheBreakpoints: false,
|
|
14224
|
+
minPrefixTokens: 1024,
|
|
14225
|
+
effortBreaksCache: true,
|
|
14226
|
+
stripVolatileHelps: true,
|
|
14227
|
+
note: "Automatic prefix cache; effort and prefix edits both reset it."
|
|
14228
|
+
},
|
|
14229
|
+
moonshot: {
|
|
14230
|
+
brand: "moonshot",
|
|
14231
|
+
label: "Moonshot",
|
|
14232
|
+
automaticPrefixCache: true,
|
|
14233
|
+
explicitCacheBreakpoints: false,
|
|
14234
|
+
minPrefixTokens: 1024,
|
|
14235
|
+
effortBreaksCache: true,
|
|
14236
|
+
stripVolatileHelps: true,
|
|
14237
|
+
note: "Automatic prefix cache; reasoning content is preserved separately."
|
|
14238
|
+
},
|
|
14239
|
+
qwen: {
|
|
14240
|
+
brand: "qwen",
|
|
14241
|
+
label: "Qwen",
|
|
14242
|
+
automaticPrefixCache: true,
|
|
14243
|
+
explicitCacheBreakpoints: false,
|
|
14244
|
+
minPrefixTokens: 1024,
|
|
14245
|
+
effortBreaksCache: false,
|
|
14246
|
+
stripVolatileHelps: true,
|
|
14247
|
+
note: "Automatic prefix cache; keep the system prompt and tool list stable."
|
|
14248
|
+
},
|
|
14249
|
+
zhipu: {
|
|
14250
|
+
brand: "zhipu",
|
|
14251
|
+
label: "Zhipu",
|
|
14252
|
+
automaticPrefixCache: true,
|
|
14253
|
+
explicitCacheBreakpoints: false,
|
|
14254
|
+
minPrefixTokens: 1024,
|
|
14255
|
+
effortBreaksCache: true,
|
|
14256
|
+
stripVolatileHelps: true,
|
|
14257
|
+
note: "Automatic prefix cache; treat a parameter change as a cache reset."
|
|
14258
|
+
},
|
|
14259
|
+
mistral: {
|
|
14260
|
+
brand: "mistral",
|
|
14261
|
+
label: "Mistral",
|
|
14262
|
+
automaticPrefixCache: true,
|
|
14263
|
+
explicitCacheBreakpoints: false,
|
|
14264
|
+
minPrefixTokens: 1024,
|
|
14265
|
+
effortBreaksCache: false,
|
|
14266
|
+
stripVolatileHelps: true,
|
|
14267
|
+
note: "Prefix caching with a conservative minimum; stable prefixes are the lever."
|
|
14268
|
+
},
|
|
14269
|
+
meta: {
|
|
14270
|
+
brand: "meta",
|
|
14271
|
+
label: "Meta",
|
|
14272
|
+
automaticPrefixCache: true,
|
|
14273
|
+
explicitCacheBreakpoints: false,
|
|
14274
|
+
minPrefixTokens: 1024,
|
|
14275
|
+
effortBreaksCache: false,
|
|
14276
|
+
stripVolatileHelps: true,
|
|
14277
|
+
note: "Self-hosted families vary by deployment; the conservative prefix rule applies."
|
|
14278
|
+
},
|
|
14279
|
+
unknown: {
|
|
14280
|
+
brand: "unknown",
|
|
14281
|
+
label: "Unknown",
|
|
14282
|
+
automaticPrefixCache: true,
|
|
14283
|
+
explicitCacheBreakpoints: false,
|
|
14284
|
+
minPrefixTokens: 1024,
|
|
14285
|
+
effortBreaksCache: true,
|
|
14286
|
+
stripVolatileHelps: true,
|
|
14287
|
+
note: "Unknown family: assume an automatic prefix cache and keep everything stable."
|
|
14288
|
+
}
|
|
14289
|
+
};
|
|
14290
|
+
/**
|
|
14291
|
+
* Brand patterns, tested against provider + model + display name.
|
|
14292
|
+
*
|
|
14293
|
+
* Order matters: the first match wins, so a reseller route named after its
|
|
14294
|
+
* model (`acme-claude`) still lands on Anthropic before any generic rule.
|
|
14295
|
+
*/
|
|
14296
|
+
const BRAND_PATTERNS = [
|
|
14297
|
+
{
|
|
14298
|
+
brand: "anthropic",
|
|
14299
|
+
pattern: /anthropic|claude/i
|
|
14300
|
+
},
|
|
14301
|
+
{
|
|
14302
|
+
brand: "openai",
|
|
14303
|
+
pattern: /openai|chatgpt|\bgpt\b|gpt-[0-9o]|\bo[134]\b/i
|
|
14304
|
+
},
|
|
14305
|
+
{
|
|
14306
|
+
brand: "google",
|
|
14307
|
+
pattern: /google|gemini|vertex/i
|
|
14308
|
+
},
|
|
14309
|
+
{
|
|
14310
|
+
brand: "deepseek",
|
|
14311
|
+
pattern: /deepseek/i
|
|
14312
|
+
},
|
|
14313
|
+
{
|
|
14314
|
+
brand: "xai",
|
|
14315
|
+
pattern: /\bxai\b|grok/i
|
|
14316
|
+
},
|
|
14317
|
+
{
|
|
14318
|
+
brand: "moonshot",
|
|
14319
|
+
pattern: /moonshot|kimi/i
|
|
14320
|
+
},
|
|
14321
|
+
{
|
|
14322
|
+
brand: "qwen",
|
|
14323
|
+
pattern: /qwen|tongyi|dashscope|alibaba/i
|
|
14324
|
+
},
|
|
14325
|
+
{
|
|
14326
|
+
brand: "zhipu",
|
|
14327
|
+
pattern: /zhipu|chatglm|\bglm\b/i
|
|
14328
|
+
},
|
|
14329
|
+
{
|
|
14330
|
+
brand: "mistral",
|
|
14331
|
+
pattern: /mistral|mixtral|codestral/i
|
|
14332
|
+
},
|
|
14333
|
+
{
|
|
14334
|
+
brand: "meta",
|
|
14335
|
+
pattern: /\bllama\b|meta-llama/i
|
|
14336
|
+
}
|
|
14337
|
+
];
|
|
14338
|
+
/** Classify one host-reported model identity onto a known brand. */
|
|
14339
|
+
function classifyModelBrand(identity) {
|
|
14340
|
+
const haystack = [
|
|
14341
|
+
identity.provider,
|
|
14342
|
+
identity.model,
|
|
14343
|
+
identity.name,
|
|
14344
|
+
identity.description
|
|
14345
|
+
].filter((part) => typeof part === "string" && part.length > 0).join(" ");
|
|
14346
|
+
if (haystack.length === 0) return "unknown";
|
|
14347
|
+
for (const row of BRAND_PATTERNS) if (row.pattern.test(haystack)) return row.brand;
|
|
14348
|
+
return "unknown";
|
|
14349
|
+
}
|
|
14350
|
+
/** The profile for one host-reported identity. */
|
|
14351
|
+
function brandProfileOf(identity) {
|
|
14352
|
+
return BRAND_PROFILES[classifyModelBrand(identity)];
|
|
14353
|
+
}
|
|
14354
|
+
/**
|
|
14355
|
+
* Whether the proxy has a useful local lever for this model.
|
|
14356
|
+
*
|
|
14357
|
+
* With an explicit-breakpoint family the honest answer is "no": the plugin can
|
|
14358
|
+
* keep bytes stable, but reuse is decided by markers it cannot write.
|
|
14359
|
+
*/
|
|
14360
|
+
function hasLocalCacheLever(profile) {
|
|
14361
|
+
return profile.automaticPrefixCache && profile.stripVolatileHelps;
|
|
14362
|
+
}
|
|
14363
|
+
/**
|
|
14364
|
+
* Whether outgoing content should be rewritten for this model right now.
|
|
14365
|
+
*
|
|
14366
|
+
* Two gates, both deliberate:
|
|
14367
|
+
* - the operator switch (`cache.enabled`) is read live, so the settings page
|
|
14368
|
+
* turns the rewrite off immediately;
|
|
14369
|
+
* - a family without an automatic prefix cache is never rewritten — rewriting
|
|
14370
|
+
* its bytes cannot improve reuse and only risks changing what the model sees.
|
|
14371
|
+
*/
|
|
14372
|
+
function shouldDecorate(profile, cacheEnabled) {
|
|
14373
|
+
return cacheEnabled && hasLocalCacheLever(profile);
|
|
14374
|
+
}
|
|
14375
|
+
//#endregion
|
|
13903
14376
|
//#region src/shared/i18n.ts
|
|
13904
14377
|
/**
|
|
13905
14378
|
* The plugin's single source of user-visible and model-facing copy (v24 §22,
|
|
@@ -14534,14 +15007,6 @@ const ZH = {
|
|
|
14534
15007
|
label: "工具结果字节上限",
|
|
14535
15008
|
hint: "工具返回给模型的字节上限;0 表示不限制。"
|
|
14536
15009
|
},
|
|
14537
|
-
"scene.thinking": {
|
|
14538
|
-
label: "思考模式",
|
|
14539
|
-
hint: "是否把推理过程摊开;对话和办公默认关闭。"
|
|
14540
|
-
},
|
|
14541
|
-
"scene.reasoningEffort": {
|
|
14542
|
-
label: "推理强度",
|
|
14543
|
-
hint: "原样传给宿主模型的强度名,例如 high;本插件不改写。"
|
|
14544
|
-
},
|
|
14545
15010
|
"scene.requirePlan": {
|
|
14546
15011
|
label: "必须出计划",
|
|
14547
15012
|
hint: "这一情景是否必须先有可评分的计划。"
|
|
@@ -14603,10 +15068,6 @@ const ZH = {
|
|
|
14603
15068
|
compact: "压缩",
|
|
14604
15069
|
permission: "权限"
|
|
14605
15070
|
},
|
|
14606
|
-
thinkingOptions: {
|
|
14607
|
-
enabled: "开启",
|
|
14608
|
-
disabled: "关闭"
|
|
14609
|
-
},
|
|
14610
15071
|
defaultValue: "默认",
|
|
14611
15072
|
scene: "情景",
|
|
14612
15073
|
allScenes: "所有情景",
|
|
@@ -15328,14 +15789,6 @@ const COPY_BY_LOCALE = {
|
|
|
15328
15789
|
label: "Tool result bytes",
|
|
15329
15790
|
hint: "Byte cap on a tool result returned to the model; 0 means no cap."
|
|
15330
15791
|
},
|
|
15331
|
-
"scene.thinking": {
|
|
15332
|
-
label: "Thinking mode",
|
|
15333
|
-
hint: "Whether reasoning is shown; off by default for dialogue and office."
|
|
15334
|
-
},
|
|
15335
|
-
"scene.reasoningEffort": {
|
|
15336
|
-
label: "Reasoning effort",
|
|
15337
|
-
hint: "Effort name passed to the host model unchanged, e.g. high."
|
|
15338
|
-
},
|
|
15339
15792
|
"scene.requirePlan": {
|
|
15340
15793
|
label: "Plan required",
|
|
15341
15794
|
hint: "Whether this scene must produce a scoreable plan first."
|
|
@@ -15397,10 +15850,6 @@ const COPY_BY_LOCALE = {
|
|
|
15397
15850
|
compact: "Compact",
|
|
15398
15851
|
permission: "Permission"
|
|
15399
15852
|
},
|
|
15400
|
-
thinkingOptions: {
|
|
15401
|
-
enabled: "On",
|
|
15402
|
-
disabled: "Off"
|
|
15403
|
-
},
|
|
15404
15853
|
defaultValue: "Default",
|
|
15405
15854
|
scene: "Scene",
|
|
15406
15855
|
allScenes: "All scenes",
|
|
@@ -27002,27 +27451,307 @@ function sessionOf(url, body) {
|
|
|
27002
27451
|
return (fromQuery ?? fromBody ?? "").trim();
|
|
27003
27452
|
}
|
|
27004
27453
|
/**
|
|
27005
|
-
*
|
|
27454
|
+
* Nest a flat dotted override map, then merge it over the deployment config.
|
|
27006
27455
|
*
|
|
27007
|
-
*
|
|
27008
|
-
*
|
|
27009
|
-
*
|
|
27010
|
-
* force and reports `persisted: false`, so the page can say "applies to this
|
|
27011
|
-
* process only" instead of claiming a durable save.
|
|
27456
|
+
* Stored overrides win over the profile patch: the operator changed those paths
|
|
27457
|
+
* from the page, which is the most recent intent. `resolveConfig` still applies
|
|
27458
|
+
* every invariant afterwards (forced flags, weight normalisation, clamping).
|
|
27012
27459
|
*/
|
|
27013
|
-
|
|
27014
|
-
const
|
|
27015
|
-
|
|
27460
|
+
function mergeOverrides(input, overrides) {
|
|
27461
|
+
const document = {};
|
|
27462
|
+
for (const [key, value] of Object.entries(overrides)) writeConfigPath(document, key, value);
|
|
27463
|
+
return deepMergeRecords(input ?? {}, document);
|
|
27464
|
+
}
|
|
27465
|
+
/** Recursive merge for plain records; arrays and scalars replace. */
|
|
27466
|
+
function deepMergeRecords(base, patch) {
|
|
27467
|
+
if (typeof base !== "object" || base === null || Array.isArray(base)) return patch;
|
|
27468
|
+
if (typeof patch !== "object" || patch === null || Array.isArray(patch)) return patch;
|
|
27469
|
+
const result = { ...base };
|
|
27470
|
+
for (const [key, value] of Object.entries(patch)) result[key] = key in result ? deepMergeRecords(result[key], value) : value;
|
|
27471
|
+
return result;
|
|
27472
|
+
}
|
|
27473
|
+
/** Resolve a Cordis service without tripping the context proxy. */
|
|
27474
|
+
function serviceOrUndefined(context, name) {
|
|
27475
|
+
const getter = context.get;
|
|
27476
|
+
if (typeof getter !== "function") return void 0;
|
|
27016
27477
|
try {
|
|
27017
|
-
|
|
27018
|
-
return
|
|
27478
|
+
const value = getter.call(context, name);
|
|
27479
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
27019
27480
|
} catch {
|
|
27020
|
-
return
|
|
27481
|
+
return;
|
|
27482
|
+
}
|
|
27483
|
+
}
|
|
27484
|
+
/** The host webserver, when the profile runs one (a headless profile does not). */
|
|
27485
|
+
function webServerOf(context) {
|
|
27486
|
+
const service = serviceOrUndefined(context, "webServer");
|
|
27487
|
+
return service !== void 0 && typeof service.register === "function" ? service : void 0;
|
|
27488
|
+
}
|
|
27489
|
+
/** The live `ctx.llm` runtime; the proxy adapter needs `stream`, not the port. */
|
|
27490
|
+
function llmRuntimeOf(context) {
|
|
27491
|
+
const service = serviceOrUndefined(context, "llm");
|
|
27492
|
+
return service !== void 0 && typeof service.stream === "function" ? service : void 0;
|
|
27493
|
+
}
|
|
27494
|
+
/** Provider route the cache proxy registers under. */
|
|
27495
|
+
const PROXY_PROVIDER = "coordination-proxy";
|
|
27496
|
+
const proxyStatus = {
|
|
27497
|
+
available: false,
|
|
27498
|
+
enabled: false,
|
|
27499
|
+
provider: PROXY_PROVIDER,
|
|
27500
|
+
forwards: 0,
|
|
27501
|
+
refusals: 0,
|
|
27502
|
+
decorate: false
|
|
27503
|
+
};
|
|
27504
|
+
/** The status snapshot handed to `GET /proxy/status`. */
|
|
27505
|
+
function cacheProxyStatus() {
|
|
27506
|
+
return {
|
|
27507
|
+
...proxyStatus,
|
|
27508
|
+
...proxyStatus.upstream === void 0 ? {} : { upstream: { ...proxyStatus.upstream } }
|
|
27509
|
+
};
|
|
27510
|
+
}
|
|
27511
|
+
/** The deployment's default model route, used when no request has named one yet. */
|
|
27512
|
+
function defaultRouteOf(context) {
|
|
27513
|
+
const service = serviceOrUndefined(context, "agentDefaultModel");
|
|
27514
|
+
const current = service?.currentSelection;
|
|
27515
|
+
if (typeof current !== "function") return void 0;
|
|
27516
|
+
try {
|
|
27517
|
+
const selection = current.call(service);
|
|
27518
|
+
const record = typeof selection === "object" && selection !== null ? selection : void 0;
|
|
27519
|
+
const provider = typeof record?.provider === "string" ? record.provider : "";
|
|
27520
|
+
const model = typeof record?.model === "string" ? record.model : "";
|
|
27521
|
+
return provider.length > 0 && model.length > 0 ? {
|
|
27522
|
+
provider,
|
|
27523
|
+
model
|
|
27524
|
+
} : void 0;
|
|
27525
|
+
} catch {
|
|
27526
|
+
return;
|
|
27021
27527
|
}
|
|
27022
27528
|
}
|
|
27529
|
+
/**
|
|
27530
|
+
* Register the experimental cache proxy.
|
|
27531
|
+
*
|
|
27532
|
+
* `agent/request` rewrites the route to the proxy provider and remembers the
|
|
27533
|
+
* real one; the adapter forwards each frozen request to it after rebuilding it.
|
|
27534
|
+
* Every step fails open: no llm runtime, no adapter registration or no event bus
|
|
27535
|
+
* simply leaves the normal path untouched.
|
|
27536
|
+
*
|
|
27537
|
+
* Security posture (see `host/proxy-adapter.ts` for the full model):
|
|
27538
|
+
* - only providers on `PROXY_ALLOWED_PROVIDERS` are ever intercepted, so the
|
|
27539
|
+
* proxy cannot redirect traffic to an endpoint this plugin invented;
|
|
27540
|
+
* - the trace file receives structural events only — provider, model, counts,
|
|
27541
|
+
* sizes — never prompt or answer text;
|
|
27542
|
+
* - the adapter holds no transport and no credential: forwarding delegates to
|
|
27543
|
+
* `ctx.llm.stream`, so the real adapter keeps authentication and egress.
|
|
27544
|
+
*
|
|
27545
|
+
* The switch is a file (`proxy.enable`) as well as an environment variable: the
|
|
27546
|
+
* desktop app is launched from the shell's environment, so a file is the only
|
|
27547
|
+
* way to turn the experiment on without restarting with new variables.
|
|
27548
|
+
*/
|
|
27549
|
+
function installCacheProxy(context, stateDir, decorate) {
|
|
27550
|
+
const flagFile = join(stateDir, "proxy.enable");
|
|
27551
|
+
const logFile = join(stateDir, "proxy.log");
|
|
27552
|
+
if (process.env.DSH_COORDINATION_PROXY !== "1" && !existsSync(flagFile)) return () => {};
|
|
27553
|
+
const llm = llmRuntimeOf(context);
|
|
27554
|
+
const service = serviceOrUndefined(context, "llm");
|
|
27555
|
+
const register = service?.registerAdapter;
|
|
27556
|
+
const on = context.on;
|
|
27557
|
+
if (llm === void 0 || typeof register !== "function" || typeof on !== "function") return () => {};
|
|
27558
|
+
/**
|
|
27559
|
+
* Append one structural event as a JSON line.
|
|
27560
|
+
*
|
|
27561
|
+
* JSON (not a formatted sentence) keeps the record unambiguous, and the event
|
|
27562
|
+
* type has no field that can carry content — a reviewer can verify the whole
|
|
27563
|
+
* leak surface by reading `ProxyTraceEvent`.
|
|
27564
|
+
*/
|
|
27565
|
+
const trace = (event) => {
|
|
27566
|
+
if (event.event === "forward") {
|
|
27567
|
+
proxyStatus.forwards += 1;
|
|
27568
|
+
proxyStatus.lastForwardAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
27569
|
+
}
|
|
27570
|
+
if (event.event === "refuse") proxyStatus.refusals += 1;
|
|
27571
|
+
const line = JSON.stringify({
|
|
27572
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27573
|
+
...event
|
|
27574
|
+
});
|
|
27575
|
+
try {
|
|
27576
|
+
appendFileSync(logFile, `${line}\n`, "utf8");
|
|
27577
|
+
} catch {}
|
|
27578
|
+
if (process.env.DSH_COORDINATION_PROXY_DEBUG === "1") console.error(`[coordination-proxy] ${line}`);
|
|
27579
|
+
};
|
|
27580
|
+
let upstream = defaultRouteOf(context);
|
|
27581
|
+
if (upstream !== void 0) {
|
|
27582
|
+
proxyStatus.upstream = { ...upstream };
|
|
27583
|
+
trace({
|
|
27584
|
+
event: "install",
|
|
27585
|
+
provider: PROXY_PROVIDER,
|
|
27586
|
+
model: upstream.model
|
|
27587
|
+
});
|
|
27588
|
+
} else trace({
|
|
27589
|
+
event: "install",
|
|
27590
|
+
provider: PROXY_PROVIDER
|
|
27591
|
+
});
|
|
27592
|
+
/**
|
|
27593
|
+
* Classify the host-reported model identity onto a built-in cache brand.
|
|
27594
|
+
*
|
|
27595
|
+
* `ctx.llm.resolveModelInfo(provider, model)` is the host's own answer for
|
|
27596
|
+
* "what model is this": it carries the display name and description that a
|
|
27597
|
+
* provider route id alone cannot. The call may hit the adapter, so it is
|
|
27598
|
+
* fired and forgotten — routing never waits on catalog I/O, and a failure
|
|
27599
|
+
* simply leaves the `unknown` conservative profile in place.
|
|
27600
|
+
*/
|
|
27601
|
+
let describedKey = "";
|
|
27602
|
+
const describeModel = (provider, model) => {
|
|
27603
|
+
const key = `${provider}\u0000${model}`;
|
|
27604
|
+
if (key === describedKey) return;
|
|
27605
|
+
describedKey = key;
|
|
27606
|
+
const llmService = serviceOrUndefined(context, "llm");
|
|
27607
|
+
const resolve = llmService?.resolveModelInfo;
|
|
27608
|
+
const apply = (identity) => {
|
|
27609
|
+
const profile = brandProfileOf(identity);
|
|
27610
|
+
proxyStatus.model = {
|
|
27611
|
+
brand: profile.brand,
|
|
27612
|
+
label: profile.label,
|
|
27613
|
+
automaticPrefixCache: profile.automaticPrefixCache,
|
|
27614
|
+
explicitCacheBreakpoints: profile.explicitCacheBreakpoints,
|
|
27615
|
+
minPrefixTokens: profile.minPrefixTokens,
|
|
27616
|
+
effortBreaksCache: profile.effortBreaksCache,
|
|
27617
|
+
stripVolatileHelps: profile.stripVolatileHelps,
|
|
27618
|
+
note: profile.note
|
|
27619
|
+
};
|
|
27620
|
+
trace({
|
|
27621
|
+
event: "model",
|
|
27622
|
+
provider,
|
|
27623
|
+
model,
|
|
27624
|
+
brand: profile.brand
|
|
27625
|
+
});
|
|
27626
|
+
};
|
|
27627
|
+
if (typeof resolve !== "function") {
|
|
27628
|
+
apply({
|
|
27629
|
+
provider,
|
|
27630
|
+
model
|
|
27631
|
+
});
|
|
27632
|
+
return;
|
|
27633
|
+
}
|
|
27634
|
+
try {
|
|
27635
|
+
const pending = resolve.call(llmService, provider, model);
|
|
27636
|
+
if (pending instanceof Promise) {
|
|
27637
|
+
pending.then((info) => {
|
|
27638
|
+
const record = typeof info === "object" && info !== null ? info : void 0;
|
|
27639
|
+
apply({
|
|
27640
|
+
provider,
|
|
27641
|
+
model,
|
|
27642
|
+
...typeof record?.name === "string" ? { name: record.name } : {},
|
|
27643
|
+
...typeof record?.description === "string" ? { description: record.description } : {}
|
|
27644
|
+
});
|
|
27645
|
+
}).catch(() => {
|
|
27646
|
+
apply({
|
|
27647
|
+
provider,
|
|
27648
|
+
model
|
|
27649
|
+
});
|
|
27650
|
+
});
|
|
27651
|
+
return;
|
|
27652
|
+
}
|
|
27653
|
+
const record = typeof pending === "object" && pending !== null ? pending : void 0;
|
|
27654
|
+
apply({
|
|
27655
|
+
provider,
|
|
27656
|
+
model,
|
|
27657
|
+
...typeof record?.name === "string" ? { name: record.name } : {},
|
|
27658
|
+
...typeof record?.description === "string" ? { description: record.description } : {}
|
|
27659
|
+
});
|
|
27660
|
+
} catch {
|
|
27661
|
+
apply({
|
|
27662
|
+
provider,
|
|
27663
|
+
model
|
|
27664
|
+
});
|
|
27665
|
+
}
|
|
27666
|
+
};
|
|
27667
|
+
if (upstream !== void 0) describeModel(upstream.provider, upstream.model ?? "");
|
|
27668
|
+
const adapter = createProxyAdapter({
|
|
27669
|
+
provider: PROXY_PROVIDER,
|
|
27670
|
+
llm,
|
|
27671
|
+
routeOf: () => upstream,
|
|
27672
|
+
allowProviders: PROXY_ALLOWED_PROVIDERS,
|
|
27673
|
+
trace,
|
|
27674
|
+
...decorate === void 0 ? {} : { decorate: (request) => decorate(request, {
|
|
27675
|
+
provider: upstream?.provider ?? "",
|
|
27676
|
+
model: upstream?.model ?? ""
|
|
27677
|
+
}) }
|
|
27678
|
+
});
|
|
27679
|
+
let registration;
|
|
27680
|
+
let detach;
|
|
27681
|
+
try {
|
|
27682
|
+
registration = register.call(service, [PROXY_PROVIDER], adapter);
|
|
27683
|
+
detach = on.call(context, "agent/request", async (_payload, next) => {
|
|
27684
|
+
const seed = await next();
|
|
27685
|
+
const record = typeof seed === "object" && seed !== null ? seed : void 0;
|
|
27686
|
+
const provider = typeof record?.provider === "string" ? record.provider : "";
|
|
27687
|
+
const model = typeof record?.model === "string" ? record.model : "";
|
|
27688
|
+
if (record === void 0 || provider.length === 0 || model.length === 0 || provider === PROXY_PROVIDER) {
|
|
27689
|
+
if (upstream === void 0) upstream = defaultRouteOf(context);
|
|
27690
|
+
return seed;
|
|
27691
|
+
}
|
|
27692
|
+
if (!isProxyProviderAllowed(provider, PROXY_ALLOWED_PROVIDERS)) {
|
|
27693
|
+
trace({
|
|
27694
|
+
event: "refuse",
|
|
27695
|
+
provider,
|
|
27696
|
+
reason: "provider-not-allowlisted"
|
|
27697
|
+
});
|
|
27698
|
+
return seed;
|
|
27699
|
+
}
|
|
27700
|
+
upstream = {
|
|
27701
|
+
provider,
|
|
27702
|
+
model
|
|
27703
|
+
};
|
|
27704
|
+
proxyStatus.upstream = { ...upstream };
|
|
27705
|
+
trace({
|
|
27706
|
+
event: "route",
|
|
27707
|
+
provider,
|
|
27708
|
+
model
|
|
27709
|
+
});
|
|
27710
|
+
describeModel(provider, model);
|
|
27711
|
+
return {
|
|
27712
|
+
...record,
|
|
27713
|
+
provider: PROXY_PROVIDER,
|
|
27714
|
+
model
|
|
27715
|
+
};
|
|
27716
|
+
}, { prepend: true });
|
|
27717
|
+
} catch {
|
|
27718
|
+
trace({
|
|
27719
|
+
event: "refuse",
|
|
27720
|
+
provider: PROXY_PROVIDER,
|
|
27721
|
+
reason: "registration-failed"
|
|
27722
|
+
});
|
|
27723
|
+
return () => {};
|
|
27724
|
+
}
|
|
27725
|
+
proxyStatus.available = true;
|
|
27726
|
+
proxyStatus.enabled = true;
|
|
27727
|
+
proxyStatus.decorate = decorate !== void 0;
|
|
27728
|
+
return () => {
|
|
27729
|
+
proxyStatus.enabled = false;
|
|
27730
|
+
proxyStatus.decorate = false;
|
|
27731
|
+
if (typeof detach === "function") try {
|
|
27732
|
+
detach();
|
|
27733
|
+
} catch {}
|
|
27734
|
+
if (typeof registration === "function") try {
|
|
27735
|
+
registration();
|
|
27736
|
+
} catch {}
|
|
27737
|
+
};
|
|
27738
|
+
}
|
|
27023
27739
|
function apply(context, configInput) {
|
|
27024
|
-
const
|
|
27740
|
+
const configured = resolveConfig(configInput);
|
|
27741
|
+
const settingsFile = settingsPath(resolveDatabasePath(configured.databasePath, configured.database));
|
|
27742
|
+
const stored = readOverrides(settingsFile);
|
|
27743
|
+
const config = Object.keys(stored).length === 0 ? configured : resolveConfig(mergeOverrides(configInput, stored));
|
|
27744
|
+
const service = new CoordinationService(context, config, createRuntime(context));
|
|
27025
27745
|
service.turn.recoverInterrupted();
|
|
27746
|
+
if (process.env.DSH_COORDINATION_PROXY === "1" || existsSync(join(dirname(settingsFile), "proxy.enable"))) {
|
|
27747
|
+
const decorate = (request, identity) => {
|
|
27748
|
+
if (!shouldDecorate(brandProfileOf(identity), config.cache.enabled)) return request;
|
|
27749
|
+
const strategy = service.provider.cacheStrategy(identity.provider);
|
|
27750
|
+
const { cacheWarning: _advice, ...outgoing } = service.cache.decorate(request, strategy);
|
|
27751
|
+
return outgoing;
|
|
27752
|
+
};
|
|
27753
|
+
context.effect(() => installCacheProxy(context, dirname(settingsFile), decorate), `${name}: cache proxy`);
|
|
27754
|
+
}
|
|
27026
27755
|
/** Re-register the tools after `POST /locale` changed the copy language. */
|
|
27027
27756
|
let refreshTools;
|
|
27028
27757
|
context.effect(() => () => {
|
|
@@ -27080,7 +27809,9 @@ function apply(context, configInput) {
|
|
|
27080
27809
|
};
|
|
27081
27810
|
}, `${name}: tools`);
|
|
27082
27811
|
context.effect(() => {
|
|
27083
|
-
const
|
|
27812
|
+
const webServer = webServerOf(context);
|
|
27813
|
+
if (webServer === void 0) return () => {};
|
|
27814
|
+
const dispose = webServer.register({
|
|
27084
27815
|
kind: "prefix",
|
|
27085
27816
|
path: ROUTE_PREFIX,
|
|
27086
27817
|
handler: async (request, response) => {
|
|
@@ -27099,6 +27830,13 @@ function apply(context, configInput) {
|
|
|
27099
27830
|
});
|
|
27100
27831
|
return;
|
|
27101
27832
|
}
|
|
27833
|
+
if (path === "/proxy/status" && method === "GET") {
|
|
27834
|
+
json(response, {
|
|
27835
|
+
ok: true,
|
|
27836
|
+
value: cacheProxyStatus()
|
|
27837
|
+
});
|
|
27838
|
+
return;
|
|
27839
|
+
}
|
|
27102
27840
|
if (path === "/echo" && method === "POST") {
|
|
27103
27841
|
json(response, {
|
|
27104
27842
|
ok: true,
|
|
@@ -27316,7 +28054,7 @@ function apply(context, configInput) {
|
|
|
27316
28054
|
if (outcome.compaction !== null) service.applyCompactionPolicy(outcome.compaction);
|
|
27317
28055
|
const appliedPatch = {};
|
|
27318
28056
|
for (const key of outcome.applied) appliedPatch[key] = patch[key];
|
|
27319
|
-
const persisted = outcome.applied.length > 0 ?
|
|
28057
|
+
const persisted = outcome.applied.length > 0 ? writeOverrides(settingsFile, appliedPatch) : false;
|
|
27320
28058
|
json(response, {
|
|
27321
28059
|
ok: true,
|
|
27322
28060
|
value: {
|
|
@@ -27371,4 +28109,4 @@ function apply(context, configInput) {
|
|
|
27371
28109
|
}, `${name}: ${ROUTE_PREFIX} routes`);
|
|
27372
28110
|
}
|
|
27373
28111
|
//#endregion
|
|
27374
|
-
export { Config, apply, inject, name };
|
|
28112
|
+
export { Config, apply, cacheProxyStatus, inject, name };
|