@dsh-plus/llm-pi 0.1.27 → 0.1.29
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/lib/client.js +126 -108
- package/lib/index.js +352 -147
- package/package.json +27 -27
- package/src/client/api.ts +8 -0
- package/src/client/card.tsx +3 -0
- package/src/client/constants.ts +12 -52
- package/src/client/fields.tsx +58 -0
- package/src/client/i18n.ts +2 -0
- package/src/client/views/compat.tsx +16 -1
- package/src/compat-gates.ts +265 -0
- package/src/compat.ts +70 -141
- package/src/config-api.ts +29 -0
- package/src/resolve-dsh.ts +54 -3
package/lib/client.js
CHANGED
|
@@ -399,6 +399,73 @@ ${extra}
|
|
|
399
399
|
}));
|
|
400
400
|
}
|
|
401
401
|
//#endregion
|
|
402
|
+
//#region src/client/constants.ts
|
|
403
|
+
/**
|
|
404
|
+
* 浏览器半内联常量:与服务端 packages/llm-pi/src/config.ts、compat.ts 逐字对齐。
|
|
405
|
+
* 浏览器半不能 import 服务端模块(tsdown 只打包 client 侧入口),
|
|
406
|
+
* 改动服务端这些常量时必须同步本文件。
|
|
407
|
+
* @module llm-pi/client/constants
|
|
408
|
+
*/
|
|
409
|
+
/** 协议枚举(来源:config.ts PROTOCOL_IDS)。 */
|
|
410
|
+
const PROTOCOL_IDS = [
|
|
411
|
+
"openai-completions",
|
|
412
|
+
"openai-responses",
|
|
413
|
+
"anthropic-messages"
|
|
414
|
+
];
|
|
415
|
+
/** thinking 档位(来源:config.ts THINKING_LEVELS)。 */
|
|
416
|
+
const THINKING_LEVELS = [
|
|
417
|
+
"off",
|
|
418
|
+
"minimal",
|
|
419
|
+
"low",
|
|
420
|
+
"medium",
|
|
421
|
+
"high",
|
|
422
|
+
"xhigh",
|
|
423
|
+
"max"
|
|
424
|
+
];
|
|
425
|
+
/** 请求模态(来源:config.ts MODALITIES)。 */
|
|
426
|
+
const MODALITIES = ["text", "image"];
|
|
427
|
+
/** cacheRetention 枚举(来源:config.ts providerProfile.cacheRetention)。 */
|
|
428
|
+
const CACHE_RETENTION_OPTIONS = [
|
|
429
|
+
"none",
|
|
430
|
+
"short",
|
|
431
|
+
"long"
|
|
432
|
+
];
|
|
433
|
+
/** transport 枚举(来源:config.ts providerProfile.transport)。 */
|
|
434
|
+
const TRANSPORT_OPTIONS = [
|
|
435
|
+
"sse",
|
|
436
|
+
"websocket",
|
|
437
|
+
"websocket-cached",
|
|
438
|
+
"auto"
|
|
439
|
+
];
|
|
440
|
+
/** thinkingBudgets 档位键(来源:config.ts thinkingBudgets)。 */
|
|
441
|
+
const BUDGET_KEYS = [
|
|
442
|
+
"minimal",
|
|
443
|
+
"low",
|
|
444
|
+
"medium",
|
|
445
|
+
"high"
|
|
446
|
+
];
|
|
447
|
+
/**
|
|
448
|
+
* 逐协议 compat 字段表:**服务端推导结果经 /catalog 下发**,浏览器半不再手抄
|
|
449
|
+
* (手抄遗漏官方新增字段 = UI 不渲染 + 保存被后端拒绝;见 compat-gates.ts)。
|
|
450
|
+
* 未拿到服务端表时用 EMPTY(UI 只渲染提示行),不放内置副本兜底——宁可少画
|
|
451
|
+
* 控件,也不给出可能与官方不符的字段集。
|
|
452
|
+
*/
|
|
453
|
+
let compatTable = {};
|
|
454
|
+
/** 安装服务端下发的字段表(card 挂载/刷新目录时调用)。 */
|
|
455
|
+
function installCompatFields(table) {
|
|
456
|
+
compatTable = table;
|
|
457
|
+
}
|
|
458
|
+
/** 某协议的全部合法 compat 键(与服务端 compatFieldsOf 一致)。 */
|
|
459
|
+
function compatFieldsOf(api) {
|
|
460
|
+
return Object.keys(compatTable[api] ?? {});
|
|
461
|
+
}
|
|
462
|
+
/** 某协议某字段的取值约束(与服务端 compatFieldSpec 一致)。 */
|
|
463
|
+
function compatFieldSpec(api, field) {
|
|
464
|
+
return compatTable[api]?.[field];
|
|
465
|
+
}
|
|
466
|
+
/** api 未设置时的渲染回退组(最常见的协议;保存仍由后端按实际协议校验)。 */
|
|
467
|
+
const COMPAT_FALLBACK_API = "openai-completions";
|
|
468
|
+
//#endregion
|
|
402
469
|
//#region src/client/draft.ts
|
|
403
470
|
function numToText(value) {
|
|
404
471
|
return value === void 0 ? "" : String(value);
|
|
@@ -737,6 +804,53 @@ ${extra}
|
|
|
737
804
|
]
|
|
738
805
|
});
|
|
739
806
|
}
|
|
807
|
+
/**
|
|
808
|
+
* 整数字段:compat 的数值型开关(如 vllmPriority,官方 z.number().step(1))。
|
|
809
|
+
* 本地保留文本态(与 JsonField 同款 epoch 播种),仅在解析为整数时回写草稿——
|
|
810
|
+
* 小数/非数字显示错误且不提交,与服务端 checkValue 的整数语义一致
|
|
811
|
+
* (后端仍做最终校验)。
|
|
812
|
+
*/
|
|
813
|
+
function IntegerField(props) {
|
|
814
|
+
const [text, setText] = (0, react.useState)(() => props.value === void 0 ? "" : String(props.value));
|
|
815
|
+
(0, react.useEffect)(() => {
|
|
816
|
+
setText(props.value === void 0 ? "" : String(props.value));
|
|
817
|
+
}, [props.epoch]);
|
|
818
|
+
const trimmed = text.trim();
|
|
819
|
+
const parsed = trimmed === "" ? void 0 : Number(trimmed);
|
|
820
|
+
const ok = trimmed === "" || parsed !== void 0 && Number.isInteger(parsed);
|
|
821
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
822
|
+
className: `lpc-field${props.wide === true ? " lpc-wide" : ""}`,
|
|
823
|
+
children: [
|
|
824
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
825
|
+
className: "lpc-head",
|
|
826
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
827
|
+
className: "lpc-label",
|
|
828
|
+
htmlFor: props.id,
|
|
829
|
+
children: props.label
|
|
830
|
+
})
|
|
831
|
+
}),
|
|
832
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
833
|
+
id: props.id,
|
|
834
|
+
className: `lpc-input${ok ? "" : " lpc-inputInvalid"}`,
|
|
835
|
+
type: "text",
|
|
836
|
+
inputMode: "numeric",
|
|
837
|
+
"aria-invalid": ok ? void 0 : true,
|
|
838
|
+
value: text,
|
|
839
|
+
disabled: props.disabled === true,
|
|
840
|
+
onChange: (event) => {
|
|
841
|
+
const next = event.target.value;
|
|
842
|
+
setText(next);
|
|
843
|
+
const value = next.trim() === "" ? void 0 : Number(next.trim());
|
|
844
|
+
props.onEdit(value !== void 0 && Number.isInteger(value) ? value : void 0);
|
|
845
|
+
}
|
|
846
|
+
}),
|
|
847
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
848
|
+
className: ok ? "lpc-hint" : "lpc-invalid",
|
|
849
|
+
children: ok ? props.hint ?? "" : props.invalidLabel
|
|
850
|
+
})
|
|
851
|
+
]
|
|
852
|
+
});
|
|
853
|
+
}
|
|
740
854
|
function SelectField(props) {
|
|
741
855
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
742
856
|
className: `lpc-field${props.wide === true ? " lpc-wide" : ""}`,
|
|
@@ -894,114 +1008,6 @@ ${extra}
|
|
|
894
1008
|
});
|
|
895
1009
|
}
|
|
896
1010
|
//#endregion
|
|
897
|
-
//#region src/client/constants.ts
|
|
898
|
-
/**
|
|
899
|
-
* 浏览器半内联常量:与服务端 packages/llm-pi/src/config.ts、compat.ts 逐字对齐。
|
|
900
|
-
* 浏览器半不能 import 服务端模块(tsdown 只打包 client 侧入口),
|
|
901
|
-
* 改动服务端这些常量时必须同步本文件。
|
|
902
|
-
* @module llm-pi/client/constants
|
|
903
|
-
*/
|
|
904
|
-
/** 协议枚举(来源:config.ts PROTOCOL_IDS)。 */
|
|
905
|
-
const PROTOCOL_IDS = [
|
|
906
|
-
"openai-completions",
|
|
907
|
-
"openai-responses",
|
|
908
|
-
"anthropic-messages"
|
|
909
|
-
];
|
|
910
|
-
/** thinking 档位(来源:config.ts THINKING_LEVELS)。 */
|
|
911
|
-
const THINKING_LEVELS = [
|
|
912
|
-
"off",
|
|
913
|
-
"minimal",
|
|
914
|
-
"low",
|
|
915
|
-
"medium",
|
|
916
|
-
"high",
|
|
917
|
-
"xhigh",
|
|
918
|
-
"max"
|
|
919
|
-
];
|
|
920
|
-
/** 请求模态(来源:config.ts MODALITIES)。 */
|
|
921
|
-
const MODALITIES = ["text", "image"];
|
|
922
|
-
/** cacheRetention 枚举(来源:config.ts providerProfile.cacheRetention)。 */
|
|
923
|
-
const CACHE_RETENTION_OPTIONS = [
|
|
924
|
-
"none",
|
|
925
|
-
"short",
|
|
926
|
-
"long"
|
|
927
|
-
];
|
|
928
|
-
/** transport 枚举(来源:config.ts providerProfile.transport)。 */
|
|
929
|
-
const TRANSPORT_OPTIONS = [
|
|
930
|
-
"sse",
|
|
931
|
-
"websocket",
|
|
932
|
-
"websocket-cached",
|
|
933
|
-
"auto"
|
|
934
|
-
];
|
|
935
|
-
/** thinkingBudgets 档位键(来源:config.ts thinkingBudgets)。 */
|
|
936
|
-
const BUDGET_KEYS = [
|
|
937
|
-
"minimal",
|
|
938
|
-
"low",
|
|
939
|
-
"medium",
|
|
940
|
-
"high"
|
|
941
|
-
];
|
|
942
|
-
/**
|
|
943
|
-
* 逐协议 compat 字段表(来源:compat.ts,逐字段镜像官方 catalog.ts COMPAT_GATES)。
|
|
944
|
-
* 只列 offer 字段(withhold 字段官方写时拒绝,UI 不提供);取值约束对齐官方
|
|
945
|
-
* config.ts compatProfile schema。
|
|
946
|
-
*/
|
|
947
|
-
const COMPAT_FIELDS = {
|
|
948
|
-
"openai-completions": {
|
|
949
|
-
supportsStore: "boolean",
|
|
950
|
-
supportsDeveloperRole: "boolean",
|
|
951
|
-
supportsReasoningEffort: "boolean",
|
|
952
|
-
supportsUsageInStreaming: "boolean",
|
|
953
|
-
supportsFinishReason: "boolean",
|
|
954
|
-
maxTokensField: ["max_completion_tokens", "max_tokens"],
|
|
955
|
-
requiresToolResultName: "boolean",
|
|
956
|
-
requiresAssistantAfterToolResult: "boolean",
|
|
957
|
-
requiresThinkingAsText: "boolean",
|
|
958
|
-
requiresReasoningContentOnAssistantMessages: "boolean",
|
|
959
|
-
thinkingFormat: [
|
|
960
|
-
"openai",
|
|
961
|
-
"deepseek",
|
|
962
|
-
"openrouter",
|
|
963
|
-
"together",
|
|
964
|
-
"baseten",
|
|
965
|
-
"zai",
|
|
966
|
-
"qwen",
|
|
967
|
-
"chat-template",
|
|
968
|
-
"qwen-chat-template",
|
|
969
|
-
"string-thinking",
|
|
970
|
-
"ant-ling"
|
|
971
|
-
],
|
|
972
|
-
chatTemplateKwargs: "object",
|
|
973
|
-
chatTemplateArgs: "object",
|
|
974
|
-
supportsThinkingTokenBudget: "boolean",
|
|
975
|
-
supportsStrictMode: "boolean",
|
|
976
|
-
cacheControlFormat: ["anthropic"],
|
|
977
|
-
supportsLongCacheRetention: "boolean"
|
|
978
|
-
},
|
|
979
|
-
"openai-responses": {
|
|
980
|
-
supportsDeveloperRole: "boolean",
|
|
981
|
-
supportsStrictMode: "boolean",
|
|
982
|
-
supportsLongCacheRetention: "boolean"
|
|
983
|
-
},
|
|
984
|
-
"anthropic-messages": {
|
|
985
|
-
supportsEagerToolInputStreaming: "boolean",
|
|
986
|
-
supportsLongCacheRetention: "boolean",
|
|
987
|
-
supportsCacheControlOnTools: "boolean",
|
|
988
|
-
supportsTemperature: "boolean",
|
|
989
|
-
forceAdaptiveThinking: "boolean",
|
|
990
|
-
allowEmptySignature: "boolean",
|
|
991
|
-
supportsStrictTools: "boolean"
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
/** 某协议的全部合法 compat 键(与服务端 compatFieldsOf 一致)。 */
|
|
995
|
-
function compatFieldsOf(api) {
|
|
996
|
-
return Object.keys(COMPAT_FIELDS[api] ?? {});
|
|
997
|
-
}
|
|
998
|
-
/** 某协议某字段的取值约束(与服务端 compatFieldSpec 一致)。 */
|
|
999
|
-
function compatFieldSpec(api, field) {
|
|
1000
|
-
return COMPAT_FIELDS[api]?.[field];
|
|
1001
|
-
}
|
|
1002
|
-
/** api 未设置时的渲染回退组(最常见的协议;保存仍由后端按实际协议校验)。 */
|
|
1003
|
-
const COMPAT_FALLBACK_API = "openai-completions";
|
|
1004
|
-
//#endregion
|
|
1005
1011
|
//#region src/client/views/compat.tsx
|
|
1006
1012
|
/** api 变更后裁剪 compat:只保留新渲染组的字段,避免保存时被后端拒绝。 */
|
|
1007
1013
|
function pruneCompatForApi(compat, api) {
|
|
@@ -1055,6 +1061,15 @@ ${extra}
|
|
|
1055
1061
|
invalidText: props.t("invalidJson"),
|
|
1056
1062
|
onEdit: (value) => setField(field, value)
|
|
1057
1063
|
}, field);
|
|
1064
|
+
if (spec === "integer" || spec === "number") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IntegerField, {
|
|
1065
|
+
id: `${props.idPrefix}-${field}`,
|
|
1066
|
+
label: field,
|
|
1067
|
+
value: props.compat[field],
|
|
1068
|
+
epoch: props.epoch,
|
|
1069
|
+
invalidLabel: props.t("invalidInteger"),
|
|
1070
|
+
disabled: props.disabled === true,
|
|
1071
|
+
onEdit: (value) => setField(field, value)
|
|
1072
|
+
}, field);
|
|
1058
1073
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
|
|
1059
1074
|
id: `${props.idPrefix}-${field}`,
|
|
1060
1075
|
label: field,
|
|
@@ -1745,6 +1760,7 @@ ${extra}
|
|
|
1745
1760
|
if (!alive) return;
|
|
1746
1761
|
setKitSource(result.kitSource ?? null);
|
|
1747
1762
|
setModelsDevStatus(result.status ?? null);
|
|
1763
|
+
if (result.compat !== void 0) installCompatFields(result.compat.fields);
|
|
1748
1764
|
}).catch(() => {});
|
|
1749
1765
|
return () => {
|
|
1750
1766
|
alive = false;
|
|
@@ -2026,6 +2042,7 @@ ${extra}
|
|
|
2026
2042
|
retryPolicy: "重试策略(JSON)",
|
|
2027
2043
|
retryPolicyHint: "dsh-llm RetryPolicy 形状;非法 JSON 不会提交。",
|
|
2028
2044
|
invalidJson: "JSON 格式错误(该字段不会提交)。",
|
|
2045
|
+
invalidInteger: "必须是整数(该字段不会提交)。",
|
|
2029
2046
|
compatGroup: "Compat 覆盖",
|
|
2030
2047
|
compatApiHint: "api 未设置时暂按 openai-completions 字段组渲染;保存时后端按实际协议校验。",
|
|
2031
2048
|
compatUnset: "未设置",
|
|
@@ -2106,6 +2123,7 @@ ${extra}
|
|
|
2106
2123
|
retryPolicy: "Retry policy (JSON)",
|
|
2107
2124
|
retryPolicyHint: "dsh-llm RetryPolicy shape; invalid JSON is not submitted.",
|
|
2108
2125
|
invalidJson: "Invalid JSON (this field will not be submitted).",
|
|
2126
|
+
invalidInteger: "Must be an integer (this field will not be submitted).",
|
|
2109
2127
|
compatGroup: "Compat overrides",
|
|
2110
2128
|
compatApiHint: "When api is unset, fields render per openai-completions; the backend validates per the effective protocol.",
|
|
2111
2129
|
compatUnset: "Unset",
|