@sema-agent/server 7.87.0 → 7.87.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brain.js +5 -3
- package/dist/capabilities/approvals-push.d.ts +47 -0
- package/dist/capabilities/approvals-push.js +4 -0
- package/dist/config-catalog.js +2 -1
- package/dist/config-invariants.d.ts +2 -2
- package/dist/config-invariants.js +8 -0
- package/dist/config.js +7 -0
- package/dist/fleet/fleet-bus.d.ts +0 -1
- package/dist/http/routes/capabilities.js +2 -0
- package/dist/http/routes/fleet.js +1 -2
- package/dist/model-provider.d.ts +77 -0
- package/dist/model-provider.js +26 -0
- package/package.json +2 -2
package/dist/brain.js
CHANGED
|
@@ -2,6 +2,7 @@ import { adjudicateModelRoute, createAnthropicBrain, createCircuitBreakerBrain,
|
|
|
2
2
|
import { normalizeBaseUrl } from "@sema-agent/core";
|
|
3
3
|
import { buildDefaultResilience } from "./config-types.js";
|
|
4
4
|
import { resolveModelApiKey } from "./key-resolver.js";
|
|
5
|
+
import { catalogBrainLane } from "./model-provider.js";
|
|
5
6
|
function isOffDeploymentRoot(baseUrl, config) {
|
|
6
7
|
if (!baseUrl)
|
|
7
8
|
return false;
|
|
@@ -74,8 +75,9 @@ export function createBrain(config, deps = {}) {
|
|
|
74
75
|
if (fallbackModel) {
|
|
75
76
|
const fbOwnApiKey = () => resolveModelApiKey(fallbackModel.name, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {}) ??
|
|
76
77
|
resolveModelApiKey(degradeTo, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {});
|
|
77
|
-
const
|
|
78
|
-
const
|
|
78
|
+
const fallbackLane = catalogBrainLane(fallbackModel.provider);
|
|
79
|
+
const anthropicLaneHop = fallbackLane === "anthropic" && anthropicBrain !== undefined;
|
|
80
|
+
const fallback = fallbackLane === "anthropic" && anthropicBrain
|
|
79
81
|
?
|
|
80
82
|
anthropicBrain
|
|
81
83
|
: createOpenAIBrain({
|
|
@@ -141,7 +143,7 @@ export function brainSummary(config) {
|
|
|
141
143
|
const target = config.degrade?.reactive ? config.models?.[config.degrade.to] : undefined;
|
|
142
144
|
if (!target)
|
|
143
145
|
return { reactiveDegradeRoutePairing: null, reactiveDegradeRouteRefusal: null };
|
|
144
|
-
if (target.provider === "anthropic" && config.anthropic) {
|
|
146
|
+
if (catalogBrainLane(target.provider) === "anthropic" && config.anthropic) {
|
|
145
147
|
return { reactiveDegradeRoutePairing: "anthropic-lane", reactiveDegradeRouteRefusal: null };
|
|
146
148
|
}
|
|
147
149
|
const declaresOwnCredential = config.modelApiKeyEnv?.[target.name] !== undefined ||
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-455 —— `GET /v1/capabilities` 的 `approvalsStreamLive` 位:**本部署的审批推送面带不带 live 事件族**。
|
|
3
|
+
*
|
|
4
|
+
* ## 病
|
|
5
|
+
* S-454(7.87.0)给 `GET /v1/approvals/stream` 接上了**第二条 diff 源** —— `listLivePending`(与
|
|
6
|
+
* `GET /v1/approvals` 的 `livePending` 同一只读函数),事件另立两词 `live_pending` / `live_resolved`。
|
|
7
|
+
* 那条源的在场性挂在协调器上:`TOOL_APPROVAL_ENABLED=false` 的部署里 `ToolApprovalCoordinator` 根本不装配
|
|
8
|
+
* ⇒ 源整个缺席、流的字节与 S-454 之前逐字相同(能力缺席 = 零变化,不是空数组;见 `streamApprovals` 的
|
|
9
|
+
* `live` 参数注)。
|
|
10
|
+
*
|
|
11
|
+
* 于是消费端(client-core 的 approvalsFeed / 运营面板)**判不出自己连的是哪一种**:两形都是一条正常开着、
|
|
12
|
+
* 正常心跳的 SSE,差别只在「某一类卡永远不会从推面来」。用「等不到帧」去推断能力缺席不是判据 —— 那正是
|
|
13
|
+
* S-454 立案时那个病的形状(代理静默卡一小时、屏上零提示),只是换到了消费端这一侧。探位就是为了把
|
|
14
|
+
* 「这台机器会不会推 live 卡」从一次运气变成一次读。
|
|
15
|
+
*
|
|
16
|
+
* ## 形(与 `mcpReconnect` / `executionLane` 同族)
|
|
17
|
+
* 布尔,谓词 = **两项合取**,逐项对着那条腿自己的在场判据写:
|
|
18
|
+
* ① **审批底座**(`checkpointStore`)—— `/v1/approvals/*` 与 `/v1/assistant/*` 整域的挂载条件
|
|
19
|
+
* (`route-table.ts` 那一行的 `enabled`)。缺席 ⇒ 端点 404,**连流都不存在**;
|
|
20
|
+
* ② **live 协调器**(`toolApproval`)—— 第二条 diff 源本人。缺席 ⇒ 端点在、但推面只有 durable park 行。
|
|
21
|
+
* 两项与路由/腿从**同一批 deps** 推导 ⇒「说 yes 但没有 live 帧」结构上不可能。
|
|
22
|
+
*
|
|
23
|
+
* 🔴 **刻意不合并进 `approvals` 或 `toolApproval`**:
|
|
24
|
+
* · `approvals` 答的是「这条车道在不在」(它为真而本位为假的部署是**常态**:没开工具审批的那些);
|
|
25
|
+
* · `toolApproval` 答的是「活体工具审批 HITL 在不在」,与「那条 SSE 推不推 live 行」不是一回事 ——
|
|
26
|
+
* 没有 checkpoint 底座的部署上它可以为真而**根本没有这条流**。
|
|
27
|
+
* 任一位单读都会对一半部署说谎,所以本位是它们的合取、而不是其中之一的别名。
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ **位为真只保证这条源在场**,不保证这一次有任何悬挂 ask、也不保证跨副本可见:live 半场是**进程内
|
|
30
|
+
* 投影**(与 `GET /v1/approvals` 的 `livePending` 逐字同限,非本件引入),durable park 行那一半才是
|
|
31
|
+
* 跨副本的。消费端据此渲「本机会推 live 卡」,不据此推断「别的副本上的卡也会到我这儿」。
|
|
32
|
+
*/
|
|
33
|
+
import type { ToolApprovalCoordinator } from "../tool-approval.js";
|
|
34
|
+
/**
|
|
35
|
+
* 部署席 → 消费端窄投影。形照 `projectExecutionLaneCapability`:**只 import 型**,不碰 config.ts / server.ts,
|
|
36
|
+
* 于是本模块是叶。
|
|
37
|
+
*
|
|
38
|
+
* 🔴 `toolApproval` 的形是 `Pick<…, "listLivePending">` 而不是 `unknown`:本位声明的是**那一只读函数**
|
|
39
|
+
* 在场,而路由层递给 `streamApprovals` 的 `live` 参数是同一个 `Pick` —— 协调器哪天把这只函数改名,
|
|
40
|
+
* 本文件与那条腿**一起**编译红,不会出现「位还说 true、腿已经没源了」。
|
|
41
|
+
*/
|
|
42
|
+
export declare function projectApprovalsStreamLiveCapability(deps: {
|
|
43
|
+
/** 审批底座(`/v1/approvals/*` 整域的挂载条件);形取 `unknown` —— 本位只问在场性,不碰店面。 */
|
|
44
|
+
readonly checkpointStore?: unknown;
|
|
45
|
+
readonly toolApproval?: Pick<ToolApprovalCoordinator, "listLivePending"> | undefined;
|
|
46
|
+
}): boolean;
|
|
47
|
+
//# sourceMappingURL=approvals-push.d.ts.map
|
package/dist/config-catalog.js
CHANGED
|
@@ -4,6 +4,7 @@ import { RESILIENCE_DEFAULTS } from "./config-types.js";
|
|
|
4
4
|
import { webSearchConfigFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
|
|
5
5
|
import { REMOTE_EXEC_PROVIDER_WORDS } from "./execution-lane-caps.js";
|
|
6
6
|
import { CENTER_READ_FACE_LIVE_KEYS } from "./config-center/read-face.js";
|
|
7
|
+
import { MODEL_PROVIDER_WORDS } from "./model-provider.js";
|
|
7
8
|
export const CATALOG_VALUE_TYPES = ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
|
|
8
9
|
export const CATALOG_TIMINGS = ["hot", "restart", "deploy", "inert"];
|
|
9
10
|
export const CATALOG_DANGER_AXES = ["security", "billing", "data"];
|
|
@@ -286,7 +287,7 @@ export const CONFIG_CATALOG = [
|
|
|
286
287
|
r("MODEL_IDLE_TIMEOUT_MS", "modelPlane", "number", "流中静默看门狗(ms;0=关)", { staticDefault: String(RESILIENCE_DEFAULTS.idleTimeoutMs.value) }),
|
|
287
288
|
r("MODEL_MAX_TOKENS", "modelPlane", "number", "主模型输出上限", { derivedDefaultNote: "缺席 ⇒ min(32000, contextWindow)", resolve: (c) => c.model.maxTokens ?? null, center: cModels("models[].maxTokens") }),
|
|
288
289
|
r("MODEL_PRESENCE_PENALTY", "modelPlane", "number", "presence_penalty 直通"),
|
|
289
|
-
r("MODEL_PROVIDER", "modelPlane", "
|
|
290
|
+
r("MODEL_PROVIDER", "modelPlane", "enum", "主模型 provider(路由选 brain;S-461 起是闭集,词表外的值启动期拒启并点名机读码 `config.model_provider_invalid`)", { enumValues: MODEL_PROVIDER_WORDS, derivedDefaultNote: "缺席:ANTHROPIC base+cred 同在 ⇒ anthropic,否则 gateway", resolve: (c) => c.model.provider, center: cModels("models[].provider") }),
|
|
290
291
|
r("MODEL_REASONING", "modelPlane", "boolean", "主模型 reasoning 声明", { staticDefault: "true", resolve: (c) => c.model.reasoning ?? null, center: cModels("models[].reasoning") }),
|
|
291
292
|
r("MODEL_REASONING_EFFORT_LEVELS", "modelPlane", "csv", "openai 端点接受的思考档集(六档子集;高档 clamp-down 不 422;anthropic 腿不铸此键;未知词拒启)", {
|
|
292
293
|
derivedDefaultNote: "缺席 ⇒ 不铸 compat,core 走保守梯(minimal|low|medium|high),xhigh/max 被 clamp 下来",
|
|
@@ -28,9 +28,9 @@ import type { ServiceConfigFlat } from "./config-types.js";
|
|
|
28
28
|
*/
|
|
29
29
|
export declare const LIVENESS_HEARTBEAT_MS = 30000;
|
|
30
30
|
/** 闭集:新增一条不变量必须在这里加词(拼错 = 编译红,而不是运行期悄悄不判)。 */
|
|
31
|
-
export type CrossInvariantId = "run-stale-heartbeat" | "bind-posture" | "bake-runner-credential" | "direct-door-anchors" | "operator-principals-required" | "bake-operator-principals" | "sensitive-write-patterns" | "leader-host-lane" | "leader-durable-store" | "device-durable-store" | "device-durable-approval";
|
|
31
|
+
export type CrossInvariantId = "run-stale-heartbeat" | "bind-posture" | "bake-runner-credential" | "direct-door-anchors" | "operator-principals-required" | "bake-operator-principals" | "sensitive-write-patterns" | "leader-host-lane" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word";
|
|
32
32
|
/** 表读的键集(取自平铺配置型 ⇒ 键名/类型永不与真配置漂)。 */
|
|
33
|
-
export type CrossInvariantView = Readonly<Pick<ServiceConfigFlat, "runStaleSec" | "authToken" | "authTokens" | "allowUnauthedWrites" | "imageBakes" | "directApprovalDoor" | "durableApproval" | "approvalHmacKeys" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "requirePrincipal" | "operatorPrincipals" | "sensitiveWritePatterns" | "leaderEnabled" | "remoteExec" | "dbBackend" | "tidb" | "pg">>;
|
|
33
|
+
export type CrossInvariantView = Readonly<Pick<ServiceConfigFlat, "runStaleSec" | "authToken" | "authTokens" | "allowUnauthedWrites" | "imageBakes" | "directApprovalDoor" | "durableApproval" | "approvalHmacKeys" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "requirePrincipal" | "operatorPrincipals" | "sensitiveWritePatterns" | "leaderEnabled" | "remoteExec" | "dbBackend" | "tidb" | "pg" | "envModel">>;
|
|
34
34
|
export interface CrossInvariantInput {
|
|
35
35
|
/** 现役配置(boot 期 = 候选自身)。 */
|
|
36
36
|
readonly live: CrossInvariantView;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assertGuardPatternsUsable } from "./deployment-governance.js";
|
|
2
|
+
import { modelProviderRefusal } from "./model-provider.js";
|
|
2
3
|
export const LIVENESS_HEARTBEAT_MS = 30_000;
|
|
3
4
|
const anyServiceAuth = (v) => Boolean(v.authToken) || Object.keys(v.authTokens ?? {}).length > 0;
|
|
4
5
|
const mustNarrowBind = (v) => Boolean(v.allowUnauthedWrites) && !anyServiceAuth(v);
|
|
@@ -129,6 +130,13 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
129
130
|
? "REMOTE_EXEC=device without DURABLE_APPROVAL=true has no park exit for an approval that outlives the device's offline window"
|
|
130
131
|
: undefined,
|
|
131
132
|
},
|
|
133
|
+
{
|
|
134
|
+
id: "model-provider-word",
|
|
135
|
+
keys: ["envModel"],
|
|
136
|
+
restartReason: "model-provider",
|
|
137
|
+
bootAnchor: { file: "src/config.ts", marker: "CROSS-INVARIANT:model-provider-word" },
|
|
138
|
+
check: ({ next }) => (next.envModel.provider === undefined ? undefined : modelProviderRefusal(next.envModel.provider)),
|
|
139
|
+
},
|
|
132
140
|
];
|
|
133
141
|
export function crossInvariant(id) {
|
|
134
142
|
const inv = CONFIG_CROSS_INVARIANTS.find((i) => i.id === id);
|
package/dist/config.js
CHANGED
|
@@ -17,6 +17,7 @@ import { readFaceForPosture } from "./read-face-posture.js";
|
|
|
17
17
|
import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat, isThinkingTier } from "./model-compat.js";
|
|
18
18
|
import {} from "./posture-source.js";
|
|
19
19
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
20
|
+
import { modelProviderRefusal } from "./model-provider.js";
|
|
20
21
|
export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
|
|
21
22
|
function csv(name) {
|
|
22
23
|
return (process.env[name] ?? "")
|
|
@@ -776,6 +777,12 @@ function parseModelDomain() {
|
|
|
776
777
|
"that made every external deployment fail far from the cause (gateway 400)");
|
|
777
778
|
}
|
|
778
779
|
const explicitProvider = process.env.MODEL_PROVIDER;
|
|
780
|
+
if (explicitProvider !== undefined && explicitProvider !== "") {
|
|
781
|
+
const refusal = modelProviderRefusal(explicitProvider);
|
|
782
|
+
if (refusal !== undefined) {
|
|
783
|
+
throw new Error(refusal);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
779
786
|
const inferredAnthropicBaseUrl = firstSetEnv("ANTHROPIC_BASE_URL");
|
|
780
787
|
const inferredAnthropicCred = firstSetEnv("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN");
|
|
781
788
|
const provider = (() => {
|
|
@@ -74,7 +74,6 @@ export interface FleetTaskRow {
|
|
|
74
74
|
startedAt?: number;
|
|
75
75
|
elapsedMs?: number;
|
|
76
76
|
tokens?: number;
|
|
77
|
-
queuedCount?: number;
|
|
78
77
|
awaitingPlanApproval?: boolean;
|
|
79
78
|
/** core 1.262.0: the child's most recent tool intent as one
|
|
80
79
|
* human line ("Bash npm test") — from the bg sink tick's `currentAction` (core's only attachment point
|
|
@@ -14,6 +14,7 @@ import { projectWriteProtectionCapability } from "../../write-protection.js";
|
|
|
14
14
|
import { projectSqlEngineCapability } from "../../sql-engine-posture.js";
|
|
15
15
|
import { projectWebSearchCapability } from "../../plugins/web-search.js";
|
|
16
16
|
import { projectExecutionLaneCapability } from "../../capabilities/execution-lane.js";
|
|
17
|
+
import { projectApprovalsStreamLiveCapability } from "../../capabilities/approvals-push.js";
|
|
17
18
|
import { workflowModelAllowlistFor, selfOrchestrationDenial } from "../../task-workflow.js";
|
|
18
19
|
import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
|
|
19
20
|
import { judgeAutoModeArming } from "../../auto-mode-face.js";
|
|
@@ -138,6 +139,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
138
139
|
mcpElicitation: Boolean(deps.elicitation),
|
|
139
140
|
askUserQuestion: Boolean(deps.question),
|
|
140
141
|
toolApproval: Boolean(deps.toolApproval),
|
|
142
|
+
approvalsStreamLive: projectApprovalsStreamLiveCapability(deps),
|
|
141
143
|
streamApproval: resolveStreamApprovalGate({
|
|
142
144
|
toolApprovalEnabled: Boolean(deps.toolApproval),
|
|
143
145
|
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
@@ -40,7 +40,7 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
|
|
|
40
40
|
const visW = (r) => (callerScope === null || r.scope === callerScope) && sessionOk(r.sessionId);
|
|
41
41
|
const SCOPED_TASK_KEY_DISPOSITION = {
|
|
42
42
|
id: "keep", parentId: "keep", parentToolCallId: "keep", workflowRunId: "keep", status: "keep",
|
|
43
|
-
startedAt: "keep", elapsedMs: "keep", tokens: "keep",
|
|
43
|
+
startedAt: "keep", elapsedMs: "keep", tokens: "keep",
|
|
44
44
|
awaitingPlanApproval: "keep", toolUses: "keep", usage: "keep", resumable: "keep", sourceLane: "keep",
|
|
45
45
|
cycleSeq: "keep", retiredBy: "keep",
|
|
46
46
|
name: "rewrite", scope: "strip", sessionId: "strip",
|
|
@@ -61,7 +61,6 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
|
|
|
61
61
|
...(rest.startedAt !== undefined ? { startedAt: rest.startedAt } : {}),
|
|
62
62
|
...(rest.elapsedMs !== undefined ? { elapsedMs: rest.elapsedMs } : {}),
|
|
63
63
|
...(rest.tokens !== undefined ? { tokens: rest.tokens } : {}),
|
|
64
|
-
...(rest.queuedCount !== undefined ? { queuedCount: rest.queuedCount } : {}),
|
|
65
64
|
...(rest.awaitingPlanApproval !== undefined ? { awaitingPlanApproval: rest.awaitingPlanApproval } : {}),
|
|
66
65
|
...(rest.toolUses !== undefined ? { toolUses: rest.toolUses } : {}),
|
|
67
66
|
...(rest.usage !== undefined ? { usage: rest.usage } : {}),
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-461 —— **模型 provider 词表的唯一属主**(闭集 + 路由腿的穷举判词 + 域外拒因)。
|
|
3
|
+
*
|
|
4
|
+
* ## 病
|
|
5
|
+
* `MODEL_PROVIDER` 此前收**任意字符串**(`config.ts` 里就是一句 `process.env.MODEL_PROVIDER`),而消费它的
|
|
6
|
+
* 只有一处「非 anthropic 即 gateway」的二分。于是最自然的误填 —— 把**厂商名**写成 provider,
|
|
7
|
+
* `MODEL_PROVIDER=deepseek` —— 起得来、静默走网关栈(实测改前读数:`model.provider="deepseek"`、
|
|
8
|
+
* `api="openai-completions"`)。运维以为自己点了一条专用路线,拿到的是默认腿,而读数上没有任何异常。
|
|
9
|
+
* 这与 [ref] 件⑤ / S-188 的三根模型线路旋钮是**同一条族律**([ref] A 档:坏值拒启,不静默折默认)。
|
|
10
|
+
*
|
|
11
|
+
* ## 词源:取不到型,就取那句自述 + 一格钉
|
|
12
|
+
* 词表的上游属主是 `@sema-agent/settings-schema` 的 `ModelEntry.provider`。它的 zod 形是
|
|
13
|
+
* `z.string().min(1).default("gateway")` —— **没有可取的型**(与 `RemoteExecProvider` / `WebSearchProvider`
|
|
14
|
+
* 那两张能取型的表不同),但它的 JSDoc 逐字写着三词与各自的腿:
|
|
15
|
+
* > Routes the brain in the service (createRoutingBrain by provider): "gateway"/"vllm" → OpenAI brain,
|
|
16
|
+
* > "anthropic" → Anthropic brain.
|
|
17
|
+
* 于是本仓建闭集常量,并由 `test/config-coverage-model-plane.test.ts` 的 S-461 ④ 格**钉住它与那句自述同集**
|
|
18
|
+
* (读的是装载的那份 dist,不手抄第二份词表)。上游哪天把词表写进 zod enum,那一格会告诉我们该改成取型。
|
|
19
|
+
*
|
|
20
|
+
* ## 三条腿各自的边界(刻意分开写)
|
|
21
|
+
* · {@link isModelProvider} / {@link modelProviderRefusal} —— **env 面**的闭集判据与拒因。它管的是
|
|
22
|
+
* `MODEL_PROVIDER`(以及缺省推断产出的那个词),执法点在 `config.ts` 的模型面装配 + `config-invariants.ts`
|
|
23
|
+
* 的 `model-provider-word` 条(热路径同判,不是第二份判据);
|
|
24
|
+
* · {@link modelBrainLane} —— **穷举 switch**([ref]:词表是闭集,加一词不补臂 = 编译红);
|
|
25
|
+
* · {@link catalogBrainLane} —— **目录词**那一侧。见它自己的头注:目录条目的 provider **不过** boot 闸,
|
|
26
|
+
* 所以那里必须有一条「认不出」的臂,而那条臂是**既有语义的书面化**,不是本批新开的兜底。
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* `MODEL_PROVIDER` 的闭集。**声明序 = 语义序**:两条 OpenAI 兼容腿在前,Anthropic 腿在后
|
|
30
|
+
* (与上游那句自述的行文同序,方便逐字对读)。
|
|
31
|
+
*/
|
|
32
|
+
export declare const MODEL_PROVIDER_WORDS: readonly ["gateway", "vllm", "anthropic"];
|
|
33
|
+
/** 闭集成员型。加一词 ⇒ {@link modelBrainLane} 的穷举 switch 当场编译红。 */
|
|
34
|
+
export type ModelProvider = (typeof MODEL_PROVIDER_WORDS)[number];
|
|
35
|
+
/** 这个词在闭集里吗(`undefined` / 空串一律 false —— 「未设」不是一个 provider,由调用点自己折缺省)。 */
|
|
36
|
+
export declare function isModelProvider(word: string | undefined): word is ModelProvider;
|
|
37
|
+
/**
|
|
38
|
+
* 域外值的**拒因**(`undefined` = 这个词合法)。
|
|
39
|
+
*
|
|
40
|
+
* 🔴 **机读码 `config.model_provider_invalid` 逐字在句子里**(clay 令 2026-09-18):拒启原话是运维与壳
|
|
41
|
+
* 唯一拿得到的东西,一句纯散文只能靠人读。码的拼法随 `config.<键名蛇形>_invalid` 既有族
|
|
42
|
+
* (`config.retention_policy_invalid` / `config.execution_env_home_dir_invalid` / `config.placement_root_invalid`),
|
|
43
|
+
* 方括号形与本仓既有那一处(`plugins/remote-env-device.ts`)逐字同形。**出现位置**:boot 期 `loadConfig()`
|
|
44
|
+
* 抛出的 Error.message ⇒ 进程以非零退出,这句话落在 **stderr 首屏**(未捕获异常的栈首行)。
|
|
45
|
+
*
|
|
46
|
+
* 拒因三件齐:**码** + **违例值**(照原样带引号,前后空格/大小写一眼可见) + **闭集三词**。
|
|
47
|
+
*/
|
|
48
|
+
export declare function modelProviderRefusal(word: string): string | undefined;
|
|
49
|
+
/** 一只 provider 词走哪条 brain 腿。闭集 ⇒ 两个值,没有第三种。 */
|
|
50
|
+
export type ModelBrainLane = "anthropic" | "gateway";
|
|
51
|
+
/**
|
|
52
|
+
* **穷举判词**([ref]:词表是闭集,加一词而不补臂是 COMPILE 错,不是运行期漏判)。
|
|
53
|
+
*
|
|
54
|
+
* `"vllm"` 与 `"gateway"` 同腿**不是**兜底,是上游那句自述的逐字转写(两者都是 OpenAI 兼容端点,
|
|
55
|
+
* 差别在部署形而不在协议);它们哪天要分家,分的是这张表,而不是某个调用点的 `if`。
|
|
56
|
+
*/
|
|
57
|
+
export declare function modelBrainLane(provider: ModelProvider): ModelBrainLane;
|
|
58
|
+
/**
|
|
59
|
+
* **目录条目**(`config.models[*].provider`)那一侧的判词。
|
|
60
|
+
*
|
|
61
|
+
* 🔴 为什么它与 {@link modelBrainLane} 不是同一只函数:两者的**输入保证不同**。
|
|
62
|
+
* · `MODEL_PROVIDER` 走 boot 闸(域外拒启)⇒ 到达 {@link modelBrainLane} 的恒是闭集词;
|
|
63
|
+
* · **目录条目**来自 settings-schema 的 `z.string()`,可以从 config-center **热应用**进来 —— 那条路上
|
|
64
|
+
* 没有、也不该有「拒启」这回事(一次热发布不能让在跑的副本死),所以这里必然存在一条「认不出」的臂。
|
|
65
|
+
*
|
|
66
|
+
* 🔴 那条臂**就是今天的语义,本批一个字节都没改**([ref]「不可避免的运行期 miss 臂必须说出来」):
|
|
67
|
+
* core 的 `createRoutingBrain({anthropic}, {fallback: gatewayStack})` 本身就把一切非 `anthropic` 的词
|
|
68
|
+
* 路由到网关栈(`brain.ts` 顶注那句 "everything else → the local gateway stack (also the fallback for an
|
|
69
|
+
* unrecognized provider)" 是它的原话)。本函数做的只是把那句散文变成**一个有名字的判词**,于是
|
|
70
|
+
* 「目录里写了个没人认识的 provider」这件事在代码里有一处可指的坐标,而不是散在两个 `=== "anthropic"` 里。
|
|
71
|
+
*
|
|
72
|
+
* ⚠️ **未闭环**(S-461 收车档登记):目录词今天仍然**无声**地落进网关腿。要让它也响亮,需要的是一条
|
|
73
|
+
* **热路径上的**告警/拒收(config-center 应用期逐条校验 + 运维可读的拒因),那是另一件事、另一台车 ——
|
|
74
|
+
* 在这里加一个 throw 会让一次热发布打死在跑的副本,方向反了。
|
|
75
|
+
*/
|
|
76
|
+
export declare function catalogBrainLane(provider: string | undefined): ModelBrainLane;
|
|
77
|
+
//# sourceMappingURL=model-provider.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const MODEL_PROVIDER_WORDS = ["gateway", "vllm", "anthropic"];
|
|
2
|
+
export function isModelProvider(word) {
|
|
3
|
+
return word !== undefined && MODEL_PROVIDER_WORDS.includes(word);
|
|
4
|
+
}
|
|
5
|
+
export function modelProviderRefusal(word) {
|
|
6
|
+
if (isModelProvider(word))
|
|
7
|
+
return undefined;
|
|
8
|
+
return (`env MODEL_PROVIDER="${word}" is not a known model provider [config.model_provider_invalid] — ` +
|
|
9
|
+
`valid: ${MODEL_PROVIDER_WORDS.join(" | ")}. ` +
|
|
10
|
+
"An unknown word used to start FINE and silently route to the gateway stack (a vendor name like " +
|
|
11
|
+
'"deepseek" in this slot is the natural mis-fill), so the operator got the default leg while believing ' +
|
|
12
|
+
"they had pinned a dedicated one — nothing in the readings showed it (#210 A 档:坏值拒启,不静默折默认).");
|
|
13
|
+
}
|
|
14
|
+
export function modelBrainLane(provider) {
|
|
15
|
+
switch (provider) {
|
|
16
|
+
case "anthropic":
|
|
17
|
+
return "anthropic";
|
|
18
|
+
case "gateway":
|
|
19
|
+
case "vllm":
|
|
20
|
+
return "gateway";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function catalogBrainLane(provider) {
|
|
24
|
+
return isModelProvider(provider) ? modelBrainLane(provider) : "gateway";
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=model-provider.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.87.
|
|
3
|
+
"version": "7.87.2",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sema-agent/core": "7.23.
|
|
57
|
+
"@sema-agent/core": "7.23.1",
|
|
58
58
|
"@sema-agent/settings-schema": "2.1.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|