@sema-agent/server 7.130.0 → 7.132.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/MIGRATION.md +396 -0
- package/README.md +13 -6
- package/README.zh-CN.md +9 -5
- package/USAGE.md +187 -50
- package/dist/approval-card.d.ts +3 -1
- package/dist/approval-card.js +2 -3
- package/dist/audit.d.ts +7 -10
- package/dist/audit.js +4 -1
- package/dist/auth-bridge.d.ts +4 -0
- package/dist/auth-bridge.js +10 -11
- package/dist/bench/s1/live-deps.js +2 -0
- package/dist/boot/deployment-mods.d.ts +31 -0
- package/dist/boot/deployment-mods.js +26 -0
- package/dist/boot/execution-env.d.ts +1 -1
- package/dist/boot/execution-env.js +19 -6
- package/dist/boot/memory-boundary.d.ts +2 -2
- package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
- package/dist/boot/read-deny-anchor-audit.js +36 -0
- package/dist/boot/resolve-spec.d.ts +1 -5
- package/dist/boot/resolve-spec.js +56 -61
- package/dist/boot/runner-deps.d.ts +19 -8
- package/dist/boot/runner-deps.js +14 -2
- package/dist/boot/session-faces.d.ts +1 -7
- package/dist/boot/stage-02-web-admin-pull.js +9 -1
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +4 -4
- package/dist/boot/stage-05-execution-env.js +7 -10
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +5 -4
- package/dist/boot/stage-07-capability-layer.d.ts +2 -2
- package/dist/boot/stage-07-capability-layer.js +3 -3
- package/dist/boot/stage-08-reapers.d.ts +2 -2
- package/dist/boot/stage-09-leader.d.ts +3 -9
- package/dist/boot/stage-10-http-server.d.ts +3 -9
- package/dist/boot/stage-10-http-server.js +12 -10
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/stores.js +5 -6
- package/dist/boot/web-admin.d.ts +0 -3
- package/dist/boot/web-admin.js +35 -35
- package/dist/brain.js +17 -22
- package/dist/budget.js +5 -4
- package/dist/caller-text-echo.d.ts +20 -0
- package/dist/caller-text-echo.js +24 -0
- package/dist/capabilities/brand-identity.d.ts +26 -0
- package/dist/capabilities/brand-identity.js +27 -0
- package/dist/capabilities/builtin-tools.js +2 -1
- package/dist/capabilities/center-plugins.js +1 -1
- package/dist/capabilities/center-prompts.d.ts +32 -59
- package/dist/capabilities/center-prompts.js +18 -32
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capabilities/engine-capabilities.js +2 -1
- package/dist/capabilities/hands-lane.d.ts +9 -6
- package/dist/capabilities/hands-lane.js +3 -3
- package/dist/capabilities/host-tool-names.d.ts +17 -0
- package/dist/capabilities/host-tool-names.js +15 -0
- package/dist/capabilities/mcp-probe.d.ts +14 -1
- package/dist/capabilities/mcp-probe.js +22 -133
- package/dist/capabilities/repo-tools.js +4 -3
- package/dist/capabilities/scenarios.d.ts +42 -47
- package/dist/capabilities/scenarios.js +19 -35
- package/dist/capabilities/select-environment-tool.js +2 -1
- package/dist/capabilities/send-user-file-tool.d.ts +0 -1
- package/dist/capabilities/send-user-file-tool.js +2 -2
- package/dist/capabilities/skills.d.ts +19 -4
- package/dist/capabilities/skills.js +30 -10
- package/dist/capabilities/tool-defer.js +2 -1
- package/dist/config-catalog.js +22 -11
- package/dist/config-invariants.js +2 -2
- package/dist/config-lkg.d.ts +9 -9
- package/dist/config-lkg.js +20 -4
- package/dist/config-provider.d.ts +3 -1
- package/dist/config-provider.js +14 -6
- package/dist/config-types.d.ts +13 -9
- package/dist/config.d.ts +2 -2
- package/dist/config.js +34 -20
- package/dist/device-ws-protocol.d.ts +1 -1
- package/dist/device-ws-protocol.js +1 -0
- package/dist/elicitation.d.ts +7 -0
- package/dist/elicitation.js +20 -5
- package/dist/env-name-allowlist-knobs.d.ts +12 -2
- package/dist/env-name-allowlist-knobs.js +5 -4
- package/dist/exec-bounds.d.ts +15 -3
- package/dist/exec-bounds.js +4 -0
- package/dist/execution-lane-caps.d.ts +9 -2
- package/dist/execution-lane-caps.js +11 -0
- package/dist/hooks/classic-stations.d.ts +86 -0
- package/dist/hooks/classic-stations.js +27 -0
- package/dist/hooks/hook-llm.d.ts +8 -5
- package/dist/hooks/hook-llm.js +80 -37
- package/dist/hooks/hook-runner.d.ts +273 -75
- package/dist/hooks/hook-runner.js +293 -317
- package/dist/http/admission.d.ts +8 -2
- package/dist/http/admission.js +103 -59
- package/dist/http/approval-sweeps.js +59 -18
- package/dist/http/checkpoint-ctx.d.ts +36 -18
- package/dist/http/checkpoint-ctx.js +14 -10
- package/dist/http/claimed-run.d.ts +13 -0
- package/dist/http/claimed-run.js +15 -0
- package/dist/http/device-placement.js +5 -10
- package/dist/http/dispatch.d.ts +0 -4
- package/dist/http/dispatch.js +6 -10
- package/dist/http/durable-run-leg.d.ts +2 -1
- package/dist/http/durable-run-leg.js +5 -3
- package/dist/http/health-self-report.d.ts +7 -5
- package/dist/http/health-self-report.js +4 -18
- package/dist/http/principal-gate.d.ts +23 -6
- package/dist/http/principal-gate.js +24 -5
- package/dist/http/refusal-body.d.ts +21 -0
- package/dist/http/refusal-body.js +4 -0
- package/dist/http/request-guards.d.ts +27 -0
- package/dist/http/request-guards.js +24 -3
- package/dist/http/resume-legs.d.ts +25 -0
- package/dist/http/resume-legs.js +48 -26
- package/dist/http/routes/approvals-assistant.js +1 -1
- package/dist/http/routes/capabilities.js +7 -17
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/rules.js +2 -2
- package/dist/http/routes/runs.js +13 -17
- package/dist/http/routes/session-sync.js +4 -2
- package/dist/http/routes/sessions.js +23 -11
- package/dist/http/routes/shared-memory.js +2 -13
- package/dist/http/routes/side-query.js +2 -3
- package/dist/http/routes/tasks.js +34 -19
- package/dist/http/scenario-read-face.d.ts +14 -0
- package/dist/http/scenario-read-face.js +5 -0
- package/dist/http/send.d.ts +13 -1
- package/dist/http/send.js +12 -1
- package/dist/http/server.d.ts +37 -23
- package/dist/http/server.js +30 -4
- package/dist/http/stored-body-replay.d.ts +10 -3
- package/dist/http/stored-body-replay.js +27 -6
- package/dist/http/submit-ctx.d.ts +25 -0
- package/dist/http/submit-ctx.js +39 -0
- package/dist/http/wire-types.d.ts +18 -5
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/lane-home-probe.d.ts +43 -0
- package/dist/lane-home-probe.js +35 -0
- package/dist/limit-refusal.d.ts +47 -0
- package/dist/limit-refusal.js +12 -0
- package/dist/main.js +1 -1
- package/dist/mcp-assertion-header.d.ts +3 -11
- package/dist/mcp-assertion-header.js +3 -9
- package/dist/mcp-tool-faces.d.ts +5 -0
- package/dist/mcp-tool-faces.js +5 -1
- package/dist/memory-scope.d.ts +1 -1
- package/dist/model-entry-refusal.d.ts +1 -7
- package/dist/model-provider.d.ts +39 -12
- package/dist/model-provider.js +19 -2
- package/dist/model-route-endpoint.d.ts +52 -12
- package/dist/model-route-endpoint.js +48 -26
- package/dist/model-route-gap.d.ts +5 -4
- package/dist/model-route-gap.js +1 -1
- package/dist/model-select.d.ts +58 -0
- package/dist/model-select.js +31 -0
- package/dist/namespaced-rule-name.d.ts +7 -4
- package/dist/namespaced-rule-name.js +4 -3
- package/dist/observability/cost-taxonomy.d.ts +7 -7
- package/dist/observability/fail-open.d.ts +53 -12
- package/dist/observability/fail-open.js +47 -7
- package/dist/observability/metrics.js +3 -1
- package/dist/observability/prompt-manifest.d.ts +47 -82
- package/dist/observability/prompt-manifest.js +119 -17
- package/dist/observability/run-terminal.js +14 -6
- package/dist/observability/secret-env-scrub.d.ts +13 -1
- package/dist/observability/secret-env-scrub.js +13 -1
- package/dist/observability/tool-trace.d.ts +16 -21
- package/dist/observability/tool-trace.js +51 -42
- package/dist/per-task-image.d.ts +7 -1
- package/dist/per-task-image.js +3 -3
- package/dist/plain-string-record.d.ts +25 -0
- package/dist/plain-string-record.js +32 -0
- package/dist/plugins/blob-backend.d.ts +68 -13
- package/dist/plugins/blob-backend.js +53 -10
- package/dist/plugins/checkpoint-store-sql.d.ts +14 -4
- package/dist/plugins/checkpoint-store-sql.js +39 -26
- package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
- package/dist/plugins/k8s-bg-scripts.js +22 -0
- package/dist/plugins/local-checkpoint-store.js +5 -2
- package/dist/plugins/local-session-store.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +147 -91
- package/dist/plugins/one-time-migrations.js +372 -208
- package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
- package/dist/plugins/permission-rule-store-sql.js +4 -18
- package/dist/plugins/pg-session-storage.d.ts +4 -5
- package/dist/plugins/pg-session-storage.js +7 -17
- package/dist/plugins/remote-env-adb.d.ts +6 -0
- package/dist/plugins/remote-env-adb.js +9 -4
- package/dist/plugins/remote-env-device.js +3 -0
- package/dist/plugins/remote-env-e2b.d.ts +1 -1
- package/dist/plugins/remote-env-e2b.js +21 -9
- package/dist/plugins/remote-env-host.d.ts +9 -9
- package/dist/plugins/remote-env-host.js +10 -32
- package/dist/plugins/remote-env-k8s.d.ts +95 -26
- package/dist/plugins/remote-env-k8s.js +265 -90
- package/dist/plugins/remote-env-local-docker.d.ts +20 -1
- package/dist/plugins/remote-env-local-docker.js +77 -9
- package/dist/plugins/remote-env-ssh.d.ts +15 -0
- package/dist/plugins/remote-env-ssh.js +109 -3
- package/dist/plugins/remote-shell.d.ts +0 -1
- package/dist/plugins/remote-shell.js +1 -2
- package/dist/plugins/retention-store-sql.js +6 -2
- package/dist/plugins/schema-consistency.d.ts +54 -0
- package/dist/plugins/schema-consistency.js +52 -0
- package/dist/plugins/session-epoch-anchor.d.ts +13 -0
- package/dist/plugins/session-epoch-anchor.js +23 -0
- package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
- package/dist/plugins/shared-memory-store-sql.js +5 -16
- package/dist/plugins/sql-driver.d.ts +141 -0
- package/dist/plugins/sql-driver.js +115 -1
- package/dist/plugins/sql-errors.d.ts +41 -0
- package/dist/plugins/sql-errors.js +36 -0
- package/dist/plugins/store-backend.js +2 -1
- package/dist/plugins/tidb-session-storage.d.ts +7 -11
- package/dist/plugins/tidb-session-storage.js +6 -16
- package/dist/plugins/tidb-session-store.js +1 -1
- package/dist/prompts-domain-validate.d.ts +17 -61
- package/dist/prompts-domain-validate.js +9 -119
- package/dist/question.d.ts +18 -9
- package/dist/question.js +21 -13
- package/dist/request-key-closure.d.ts +4 -4
- package/dist/request-key-closure.js +18 -13
- package/dist/rule-entry-list.d.ts +134 -24
- package/dist/rule-entry-list.js +162 -30
- package/dist/run-local.d.ts +3 -3
- package/dist/run-local.js +26 -25
- package/dist/runs.d.ts +48 -2
- package/dist/runs.js +61 -2
- package/dist/server-secret-env.d.ts +44 -18
- package/dist/server-secret-env.js +49 -8
- package/dist/session-incarnation.d.ts +15 -5
- package/dist/session-incarnation.js +16 -9
- package/dist/session-titler.js +12 -2
- package/dist/spec-fields.d.ts +34 -18
- package/dist/spec-fields.js +46 -53
- package/dist/sql-engine-posture.d.ts +29 -4
- package/dist/sql-engine-posture.js +36 -1
- package/dist/stored-request-hooks-m20.d.ts +23 -0
- package/dist/stored-request-hooks-m20.js +124 -0
- package/dist/stored-request-tool-axes-m19.d.ts +20 -5
- package/dist/stored-request-tool-axes-m19.js +30 -2
- package/dist/task-a2a.d.ts +9 -1
- package/dist/task-a2a.js +73 -47
- package/dist/task-mcp.d.ts +54 -102
- package/dist/task-mcp.js +194 -92
- package/dist/task-settings.d.ts +47 -25
- package/dist/task-settings.js +46 -61
- package/dist/text-bidi.d.ts +3 -46
- package/dist/text-bidi.js +3 -3
- package/dist/tool-approval.d.ts +1 -1
- package/dist/trace/core-keyset-guard.d.ts +24 -4
- package/dist/trace/engine-notice-wire.d.ts +11 -3
- package/dist/trace/engine-notice-wire.js +8 -1
- package/dist/trace/ledger-events.d.ts +3 -1
- package/dist/trace/ledger-sink.d.ts +29 -0
- package/dist/trace/ledger-sink.js +15 -1
- package/dist/trace/model-segment.js +1 -1
- package/dist/trace/project.d.ts +25 -6
- package/dist/trace/project.js +68 -22
- package/dist/trace/wire-projection-faces.d.ts +80 -2
- package/dist/trace/wire-projection-faces.js +23 -1
- package/dist/user-authored-text.d.ts +7 -0
- package/dist/user-authored-text.js +3 -0
- package/dist/web-admin-client/apply-effective.js +10 -5
- package/dist/web-admin-client/candidate-gate.d.ts +6 -6
- package/dist/web-admin-client/candidate-gate.js +3 -3
- package/dist/web-admin-client/http-client.js +0 -6
- package/dist/web-admin-client/read-warnings.d.ts +13 -0
- package/dist/web-admin-client/read-warnings.js +10 -0
- package/dist/web-admin-client/skills-mcp.d.ts +3 -0
- package/dist/web-admin-client/skills-mcp.js +9 -6
- package/dist/web-admin-client/types.d.ts +3 -4
- package/dist/workspace-cache.d.ts +35 -0
- package/dist/workspace-cache.js +36 -0
- package/dist/workspace-space.d.ts +3 -2
- package/dist/workspace-space.js +2 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +8 -1
- package/package.json +4 -4
- package/scripts/drain-check-local.mjs +37 -3
- package/scripts/drain-check.mysql.sql +30 -2
- package/scripts/drain-check.pg.sql +29 -2
- package/dist/capabilities/memory-notice.d.ts +0 -138
- package/dist/capabilities/memory-notice.js +0 -64
- package/dist/capabilities/prompt.d.ts +0 -17
- package/dist/capabilities/prompt.js +0 -6
- package/dist/mcp-content-origin.d.ts +0 -34
- package/dist/mcp-content-origin.js +0 -10
- package/dist/tool-axis-words.d.ts +0 -27
- package/dist/tool-axis-words.js +0 -9
- package/dist/wire-name-bound.d.ts +0 -14
- package/dist/wire-name-bound.js +0 -13
package/dist/hooks/hook-llm.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { matchCatalogModel, refusedCatalogEntry } from "../model-select.js";
|
|
1
|
+
import { admitRouteRequest, BrainError, joinRequestUrl, resolveRouteCredential, routeKeyOf } from "@sema-agent/core";
|
|
2
|
+
import { matchCatalogModel, refusedCatalogEntry, servedModelTableLeg } from "../model-select.js";
|
|
3
3
|
import { modelUnavailableMessage } from "../model-entry-refusal.js";
|
|
4
4
|
import { resolveOpenaiRouteEndpoint } from "../model-route-endpoint.js";
|
|
5
|
-
import {
|
|
5
|
+
import { anthropicRouteRoot, brainLaneFor, routeTableOf } from "../model-provider.js";
|
|
6
6
|
import { egressRedirectRefusal } from "../egress-redirect.js";
|
|
7
7
|
import { buildDisplaySafeRefusal, redactSecrets } from "../trace/redact.js";
|
|
8
|
+
const unresolvedPick = (error) => ({ ok: false, error, code: "model_unresolved" });
|
|
8
9
|
export function buildHookFailureText(raw) {
|
|
9
10
|
if (raw === "")
|
|
10
11
|
return raw;
|
|
@@ -15,68 +16,86 @@ export function buildHookFailureText(raw) {
|
|
|
15
16
|
return `[hook failure text withheld: redactor threw ${err instanceof Error ? err.name : typeof err}]`;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
const ROUTE_REFUSAL_CODES = {
|
|
20
|
+
"route.credential_mismatch": true,
|
|
21
|
+
"route.credential_missing": true,
|
|
22
|
+
"route.credential_origin_mismatch": true,
|
|
23
|
+
"route.credential_answer_conflict": true,
|
|
24
|
+
};
|
|
25
|
+
const ROUTE_REFUSAL_LEAD = "[invalid_request] ";
|
|
26
|
+
function gateRefusal(pick, e, declaredRoot) {
|
|
27
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
28
|
+
const text = e instanceof BrainError && raw.startsWith(ROUTE_REFUSAL_LEAD) ? raw.slice(ROUTE_REFUSAL_LEAD.length) : raw;
|
|
29
|
+
const code = Object.keys(ROUTE_REFUSAL_CODES).find((c) => text.startsWith(`${c}:`)) ?? "unclassified";
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
code: "route_refused",
|
|
33
|
+
error: `hook model "${pick.catalogRef}" cannot be routed (${code}) — ` + buildDisplaySafeRefusal(text, [pick.baseUrl, declaredRoot ?? ""]),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
18
36
|
export function createHookLlm(deps) {
|
|
19
37
|
const { config, metrics } = deps;
|
|
20
|
-
const anthropicRouteBaseUrl = () => config.anthropic
|
|
21
|
-
const shapeFrom = (
|
|
22
|
-
const
|
|
38
|
+
const anthropicRouteBaseUrl = () => anthropicRouteRoot(config.anthropic ?? {});
|
|
39
|
+
const shapeFrom = (m, providerModelId = m.id) => {
|
|
40
|
+
const catalogRef = m.name;
|
|
41
|
+
const lane = brainLaneFor(m.provider, routeTableOf(config));
|
|
23
42
|
switch (lane) {
|
|
24
43
|
case "anthropic":
|
|
25
|
-
return { ok: true, catalogRef, providerModelId, baseUrl: m
|
|
44
|
+
return { ok: true, catalogRef, providerModelId, baseUrl: m.baseUrl || anthropicRouteBaseUrl(), api: "anthropic-messages" };
|
|
26
45
|
case "gateway": {
|
|
27
|
-
const endpoint = resolveOpenaiRouteEndpoint(catalogRef, m
|
|
28
|
-
return endpoint.ok ? { ok: true, catalogRef, providerModelId, baseUrl: endpoint.baseUrl, api: "openai-completions" } :
|
|
46
|
+
const endpoint = resolveOpenaiRouteEndpoint(catalogRef, m, config.gatewayBaseUrl, servedModelTableLeg(config));
|
|
47
|
+
return endpoint.ok ? { ok: true, catalogRef, providerModelId, baseUrl: endpoint.baseUrl, api: "openai-completions" } : unresolvedPick(endpoint.refusal);
|
|
29
48
|
}
|
|
30
49
|
}
|
|
31
50
|
};
|
|
32
51
|
const hookModelFor = (requested) => {
|
|
33
|
-
const
|
|
52
|
+
const ownEntry = (ref) => (Object.hasOwn(config.models, ref) ? config.models[ref] : undefined);
|
|
34
53
|
const unavailable = (field, ref) => {
|
|
35
|
-
if (
|
|
54
|
+
if (ownEntry(ref) !== undefined)
|
|
36
55
|
return undefined;
|
|
37
56
|
const hit = refusedCatalogEntry(ref, config.modelRefusals);
|
|
38
|
-
return hit === undefined ? undefined :
|
|
57
|
+
return hit === undefined ? undefined : unresolvedPick(modelUnavailableMessage(field, ref, hit.name, hit.refusal));
|
|
39
58
|
};
|
|
40
59
|
if (requested === undefined) {
|
|
41
60
|
const sum = config.roles.summarize;
|
|
42
61
|
const roleModel = typeof sum === "string" ? sum : sum && typeof sum === "object" && "model" in sum && typeof sum.model === "string" ? sum.model : undefined;
|
|
43
|
-
if (roleModel !== undefined)
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
if (roleModel !== undefined) {
|
|
63
|
+
const entry = ownEntry(roleModel);
|
|
64
|
+
return unavailable("summarize role model", roleModel) ?? (entry ? shapeFrom(entry) : shapeFrom(config.model, roleModel));
|
|
65
|
+
}
|
|
66
|
+
return shapeFrom(config.model);
|
|
46
67
|
}
|
|
47
|
-
|
|
48
|
-
|
|
68
|
+
const matchedKey = matchCatalogModel(requested, config.models, config.modelRefusals);
|
|
69
|
+
if (matchedKey !== undefined)
|
|
70
|
+
return shapeFrom(config.models[matchedKey]);
|
|
49
71
|
if (Object.hasOwn(config.modelRefusals ?? {}, requested))
|
|
50
72
|
return unavailable("hook model", requested);
|
|
51
|
-
const byId = matchCatalogModel(requested, config.models, config.modelRefusals);
|
|
52
|
-
if (byId !== undefined)
|
|
53
|
-
return shape(byId);
|
|
54
73
|
if (requested === config.model.id)
|
|
55
|
-
return shapeFrom(
|
|
56
|
-
return unavailable("hook model", requested) ??
|
|
74
|
+
return shapeFrom(config.model);
|
|
75
|
+
return unavailable("hook model", requested) ?? unresolvedPick(`hook model "${requested}" is not in this deployment's catalog`);
|
|
57
76
|
};
|
|
58
77
|
const judgeRoute = (pick, perModelKey, deploymentKey, declaredRoot) => {
|
|
59
78
|
const credential = resolveRouteCredential({
|
|
60
79
|
...(perModelKey !== undefined ? { optionsApiKey: perModelKey } : {}),
|
|
61
80
|
...(deploymentKey !== undefined ? { configApiKey: deploymentKey } : {}),
|
|
62
81
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
ok: false,
|
|
67
|
-
error: `hook model "${pick.catalogRef}" cannot be routed (${verdict.code}) — ` +
|
|
68
|
-
buildDisplaySafeRefusal(routeRefusalText(verdict), [pick.baseUrl, declaredRoot ?? ""]),
|
|
69
|
-
};
|
|
82
|
+
let admission;
|
|
83
|
+
try {
|
|
84
|
+
admission = admitRouteRequest({ id: pick.catalogRef, baseUrl: pick.baseUrl }, credential, { ...(declaredRoot !== undefined ? { baseUrl: declaredRoot } : {}) });
|
|
70
85
|
}
|
|
71
|
-
|
|
86
|
+
catch (e) {
|
|
87
|
+
return gateRefusal(pick, e, declaredRoot);
|
|
88
|
+
}
|
|
89
|
+
if (credential.source === "absent" && routeKeyOf(pick.baseUrl) !== routeKeyOf(declaredRoot)) {
|
|
72
90
|
return {
|
|
73
91
|
ok: false,
|
|
92
|
+
code: "route_refused",
|
|
74
93
|
error: `hook model "${pick.catalogRef}" points at ${pick.baseUrl}, which this deployment did not declare as its own endpoint, ` +
|
|
75
94
|
`and no per-model credential resolved — refusing to send the prompt there (it embeds user content; an unauthenticated ` +
|
|
76
95
|
`request still hands that content to whoever operates that URL)`,
|
|
77
96
|
};
|
|
78
97
|
}
|
|
79
|
-
return { ok: true, credential };
|
|
98
|
+
return { ok: true, credential, admission };
|
|
80
99
|
};
|
|
81
100
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
82
101
|
const callLlm = async ({ prompt, model, timeoutMs, system, maxOutputTokens }) => {
|
|
@@ -85,6 +104,7 @@ export function createHookLlm(deps) {
|
|
|
85
104
|
return pick;
|
|
86
105
|
const ac = new AbortController();
|
|
87
106
|
const t = setTimeout(() => ac.abort(), timeoutMs);
|
|
107
|
+
let phase = "assemble";
|
|
88
108
|
try {
|
|
89
109
|
const keyResolver = deps.getKeyResolver();
|
|
90
110
|
let perModelKey;
|
|
@@ -93,17 +113,21 @@ export function createHookLlm(deps) {
|
|
|
93
113
|
perModelKey = (await keyResolver({ name: pick.catalogRef }))?.apiKey;
|
|
94
114
|
}
|
|
95
115
|
catch (e) {
|
|
96
|
-
return { ok: false, error: `per-model key resolution for "${pick.catalogRef}" failed — refusing to fall back to the shared route credential (${String(e)})
|
|
116
|
+
return { ok: false, error: `per-model key resolution for "${pick.catalogRef}" failed — refusing to fall back to the shared route credential (${String(e)})`, code: "credential_custody_failed" };
|
|
97
117
|
}
|
|
98
118
|
}
|
|
99
119
|
let url;
|
|
100
120
|
let headers;
|
|
101
121
|
let body;
|
|
122
|
+
let admitted;
|
|
123
|
+
let declaredRoot;
|
|
102
124
|
if (pick.api === "anthropic-messages") {
|
|
103
125
|
const routeBase = anthropicRouteBaseUrl();
|
|
126
|
+
declaredRoot = routeBase;
|
|
104
127
|
const routed = judgeRoute(pick, perModelKey, config.anthropic?.authToken ?? config.anthropic?.apiKey, routeBase);
|
|
105
128
|
if (!routed.ok)
|
|
106
129
|
return routed;
|
|
130
|
+
admitted = routed.admission;
|
|
107
131
|
url = joinRequestUrl(pick.baseUrl, "/v1/messages");
|
|
108
132
|
headers = {
|
|
109
133
|
"content-type": "application/json",
|
|
@@ -121,29 +145,47 @@ export function createHookLlm(deps) {
|
|
|
121
145
|
body = JSON.stringify({ model: pick.providerModelId, max_tokens: maxTokens, ...(system ? { system } : {}), messages: [{ role: "user", content: prompt }] });
|
|
122
146
|
}
|
|
123
147
|
else {
|
|
148
|
+
declaredRoot = config.gatewayBaseUrl;
|
|
124
149
|
const routed = judgeRoute(pick, perModelKey, config.gatewayApiKey, config.gatewayBaseUrl);
|
|
125
150
|
if (!routed.ok)
|
|
126
151
|
return routed;
|
|
152
|
+
admitted = routed.admission;
|
|
127
153
|
const hookKey = routed.credential.apiKey;
|
|
128
154
|
url = joinRequestUrl(pick.baseUrl, "/chat/completions");
|
|
129
155
|
headers = { "content-type": "application/json", ...(hookKey ? { authorization: `Bearer ${hookKey}` } : {}) };
|
|
130
156
|
body = JSON.stringify({ model: pick.providerModelId, ...(maxOutputTokens !== undefined ? { max_tokens: maxOutputTokens } : {}), messages: [...(system ? [{ role: "system", content: system }] : []), { role: "user", content: prompt }], stream: false });
|
|
131
157
|
}
|
|
158
|
+
try {
|
|
159
|
+
admitted();
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
return gateRefusal(pick, e, declaredRoot);
|
|
163
|
+
}
|
|
164
|
+
phase = "transport";
|
|
132
165
|
const r = await fetchImpl(url, { method: "POST", headers, body, signal: ac.signal, redirect: "manual" });
|
|
133
166
|
const redirected = egressRedirectRefusal(r, "declare the final endpoint as this route's base URL");
|
|
134
167
|
if (redirected !== undefined)
|
|
135
168
|
return { ok: false, error: `hook llm ${redirected}`, code: "redirect_refused" };
|
|
136
169
|
if (!r.ok)
|
|
137
|
-
return { ok: false, error: `hook llm HTTP ${r.status}
|
|
170
|
+
return { ok: false, error: `hook llm HTTP ${r.status}`, code: "bad_response" };
|
|
171
|
+
let answered;
|
|
172
|
+
try {
|
|
173
|
+
answered = await r.json();
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
if (ac.signal.aborted || e?.name !== "SyntaxError")
|
|
177
|
+
throw e;
|
|
178
|
+
return { ok: false, error: "hook llm answered with a body that is not JSON", code: "bad_response" };
|
|
179
|
+
}
|
|
138
180
|
let text;
|
|
139
181
|
if (pick.api === "anthropic-messages") {
|
|
140
|
-
const j =
|
|
141
|
-
const parts = (Array.isArray(j
|
|
182
|
+
const j = answered;
|
|
183
|
+
const parts = (Array.isArray(j?.content) ? j.content : []).filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text);
|
|
142
184
|
text = parts.length > 0 ? parts.join("") : undefined;
|
|
143
185
|
}
|
|
144
186
|
else {
|
|
145
|
-
const j =
|
|
146
|
-
text = j
|
|
187
|
+
const j = answered;
|
|
188
|
+
text = j?.choices?.[0]?.message?.content ?? undefined;
|
|
147
189
|
}
|
|
148
190
|
metrics.inc("hook_llm_calls_total", { type: "prompt" });
|
|
149
191
|
return typeof text === "string" ? { ok: true, text } : { ok: false, error: "hook llm returned no content", code: "no_content" };
|
|
@@ -153,6 +195,7 @@ export function createHookLlm(deps) {
|
|
|
153
195
|
return {
|
|
154
196
|
ok: false,
|
|
155
197
|
error: ac.signal.aborted ? "hook llm timeout" : `hook llm request to ${pick.baseUrl} failed: ${String(e)}${cause ? ` (${String(cause)})` : ""}`,
|
|
198
|
+
code: ac.signal.aborted || phase === "transport" ? "unreachable" : "route_refused",
|
|
156
199
|
};
|
|
157
200
|
}
|
|
158
201
|
finally {
|