@sema-agent/server 7.79.2 → 7.80.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/dist/boot/late-binds.d.ts +35 -0
- package/dist/boot/late-binds.js +20 -0
- package/dist/boot/stage-01-config.d.ts +7 -0
- package/dist/boot/stage-01-config.js +60 -0
- package/dist/boot/stage-02-registry-pull.d.ts +10 -0
- package/dist/boot/stage-02-registry-pull.js +11 -0
- package/dist/boot/stage-03-store-backend.d.ts +32 -0
- package/dist/boot/stage-03-store-backend.js +11 -0
- package/dist/boot/stage-04-stores.d.ts +69 -0
- package/dist/boot/stage-04-stores.js +49 -0
- package/dist/boot/stage-05-execution-env.d.ts +82 -0
- package/dist/boot/stage-05-execution-env.js +20 -0
- package/dist/boot/stage-06-runners.d.ts +143 -0
- package/dist/boot/stage-06-runners.js +207 -0
- package/dist/boot/stage-07-capability-layer.d.ts +157 -0
- package/dist/boot/stage-07-capability-layer.js +162 -0
- package/dist/boot/stage-08-reapers.d.ts +172 -0
- package/dist/boot/stage-08-reapers.js +78 -0
- package/dist/boot/stage-09-leader.d.ts +204 -0
- package/dist/boot/stage-09-leader.js +47 -0
- package/dist/boot/stage-10-http-server.d.ts +204 -0
- package/dist/boot/stage-10-http-server.js +289 -0
- package/dist/config-invariants.d.ts +9 -0
- package/dist/config-invariants.js +7 -1
- package/dist/config-types.d.ts +1 -1
- package/dist/http/dispatch.js +28 -8
- package/dist/http/route-ctx.d.ts +101 -4
- package/dist/http/route-ctx.js +4 -0
- package/dist/http/route-table.d.ts +71 -43
- package/dist/http/route-table.js +111 -187
- package/dist/http/routes/a2a-serve.d.ts +18 -3
- package/dist/http/routes/a2a-serve.js +25 -23
- package/dist/http/routes/admin-config-refresh.d.ts +9 -2
- package/dist/http/routes/admin-config-refresh.js +32 -31
- package/dist/http/routes/admin-drain.d.ts +9 -2
- package/dist/http/routes/admin-drain.js +32 -31
- package/dist/http/routes/admin-memory-optout.d.ts +28 -2
- package/dist/http/routes/admin-memory-optout.js +26 -39
- package/dist/http/routes/adoption.d.ts +15 -2
- package/dist/http/routes/adoption.js +52 -52
- package/dist/http/routes/agents-roster.d.ts +9 -2
- package/dist/http/routes/agents-roster.js +82 -80
- package/dist/http/routes/approvals-assistant.d.ts +58 -5
- package/dist/http/routes/approvals-assistant.js +37 -38
- package/dist/http/routes/attachments.d.ts +22 -2
- package/dist/http/routes/attachments.js +81 -72
- package/dist/http/routes/capabilities.d.ts +21 -2
- package/dist/http/routes/capabilities.js +174 -170
- package/dist/http/routes/config-catalog.d.ts +9 -2
- package/dist/http/routes/config-catalog.js +28 -31
- package/dist/http/routes/devices.d.ts +24 -2
- package/dist/http/routes/devices.js +19 -21
- package/dist/http/routes/diagnostics.d.ts +9 -2
- package/dist/http/routes/diagnostics.js +41 -43
- package/dist/http/routes/fleet.d.ts +11 -2
- package/dist/http/routes/fleet.js +23 -22
- package/dist/http/routes/images.d.ts +63 -9
- package/dist/http/routes/images.js +53 -46
- package/dist/http/routes/leader.d.ts +20 -2
- package/dist/http/routes/leader.js +43 -40
- package/dist/http/routes/memory-bundle.d.ts +15 -2
- package/dist/http/routes/memory-bundle.js +38 -38
- package/dist/http/routes/memory-compliance.d.ts +16 -4
- package/dist/http/routes/memory-compliance.js +44 -45
- package/dist/http/routes/memory-consolidation.d.ts +14 -2
- package/dist/http/routes/memory-consolidation.js +66 -66
- package/dist/http/routes/memory-origin.d.ts +23 -4
- package/dist/http/routes/memory-origin.js +65 -65
- package/dist/http/routes/memory-policy.d.ts +21 -2
- package/dist/http/routes/memory-policy.js +119 -115
- package/dist/http/routes/notify-wake.d.ts +21 -11
- package/dist/http/routes/notify-wake.js +118 -116
- package/dist/http/routes/observability.d.ts +16 -2
- package/dist/http/routes/observability.js +54 -51
- package/dist/http/routes/retention-ops.d.ts +19 -2
- package/dist/http/routes/retention-ops.js +20 -22
- package/dist/http/routes/rules.d.ts +36 -2
- package/dist/http/routes/rules.js +22 -24
- package/dist/http/routes/run-memory-capture.d.ts +10 -3
- package/dist/http/routes/run-memory-capture.js +84 -84
- package/dist/http/routes/runs.d.ts +102 -22
- package/dist/http/routes/runs.js +71 -88
- package/dist/http/routes/session-sync.d.ts +41 -2
- package/dist/http/routes/session-sync.js +25 -23
- package/dist/http/routes/sessions-list.d.ts +16 -2
- package/dist/http/routes/sessions-list.js +78 -75
- package/dist/http/routes/sessions.d.ts +77 -2
- package/dist/http/routes/sessions.js +50 -47
- package/dist/http/routes/shared-memory.d.ts +19 -2
- package/dist/http/routes/shared-memory.js +65 -63
- package/dist/http/routes/side-query.d.ts +11 -2
- package/dist/http/routes/side-query.js +81 -79
- package/dist/http/routes/tasks.d.ts +17 -2
- package/dist/http/routes/tasks.js +18 -11
- package/dist/http/routes/trace-usage.d.ts +40 -2
- package/dist/http/routes/trace-usage.js +46 -35
- package/dist/http/routes/workflows.d.ts +35 -3
- package/dist/http/routes/workflows.js +29 -29
- package/dist/main.js +20 -870
- package/dist/service-version.d.ts +5 -0
- package/dist/service-version.js +12 -0
- package/dist/trace/redact.d.ts +7 -6
- package/dist/trace/redact.js +419 -229
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SHARED_MEMORY_READ_CAP_BYTES, SharedMemoryStoreError } from "@sema-agent/core";
|
|
2
2
|
import { sendJson, sendError } from "../send.js";
|
|
3
3
|
import { gatedPrincipal } from "../principal-gate.js";
|
|
4
|
-
|
|
4
|
+
import { assertNever } from "../route-ctx.js";
|
|
5
5
|
const RETRY_AFTER_SEC = 10;
|
|
6
6
|
const HTTP_LIST_LIMIT = 100;
|
|
7
7
|
const FORBIDDEN_PATH_CHARS = /[\p{Cc}\p{Cf}\p{Co}\p{Cn}\p{Default_Ignorable_Code_Point}\u2028\u2029\\]/u;
|
|
@@ -75,76 +75,78 @@ function resolveStore(res, snapshot, storeId) {
|
|
|
75
75
|
}
|
|
76
76
|
return found.reader;
|
|
77
77
|
}
|
|
78
|
-
export async function handleSharedMemory(req, res,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
sendJson(res, 200, { state: "connecting", stores: [] });
|
|
78
|
+
export async function handleSharedMemory(req, res, match, ctx) {
|
|
79
|
+
switch (match.decl.id) {
|
|
80
|
+
case "shared-memory-stores": {
|
|
81
|
+
const snapshot = await bindSnapshot(req, res, ctx);
|
|
82
|
+
if (!snapshot)
|
|
83
|
+
return;
|
|
84
|
+
if (snapshot.state !== "connected") {
|
|
85
|
+
sendJson(res, 200, { state: "connecting", stores: [] });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
sendJson(res, 200, { state: "connected", stores: snapshot.stores.map((s) => ({ id: s.info.id, description: s.info.description, writable: s.info.writable })) });
|
|
90
89
|
return;
|
|
91
90
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
return
|
|
91
|
+
case "shared-memory-store-documents":
|
|
92
|
+
case "shared-memory-store-document": {
|
|
93
|
+
const storeId = decodeURIComponent(match.params.store);
|
|
94
|
+
const query = new URLSearchParams(match.search);
|
|
95
|
+
const snapshot = await bindSnapshot(req, res, ctx);
|
|
96
|
+
if (!snapshot)
|
|
97
|
+
return;
|
|
98
|
+
const reader = resolveStore(res, snapshot, storeId);
|
|
99
|
+
if (!reader)
|
|
100
|
+
return;
|
|
101
|
+
if (match.decl.id === "shared-memory-store-documents") {
|
|
102
|
+
const prefix = query.get("path_prefix") ?? undefined;
|
|
103
|
+
const cursor = query.get("cursor") ?? undefined;
|
|
104
|
+
const page = await reader.list(prefix, { ...(cursor !== undefined ? { cursor } : {}), limit: HTTP_LIST_LIMIT }).catch((err) => {
|
|
105
|
+
if (foldReaderError(res, err))
|
|
106
|
+
return undefined;
|
|
107
|
+
throw err;
|
|
108
|
+
});
|
|
109
|
+
if (page === undefined)
|
|
110
|
+
return;
|
|
111
|
+
const entries = Array.isArray(page) ? page : page.entries;
|
|
112
|
+
const nextCursor = Array.isArray(page) ? undefined : page.nextCursor;
|
|
113
|
+
sendJson(res, 200, { store: storeId, entries, ...(nextCursor !== undefined ? { nextCursor } : {}) });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const path = query.get("path");
|
|
117
|
+
if (path === null || path === "" || !isDocumentPath(path)) {
|
|
118
|
+
sendError(res, 400, "request.query_invalid", "missing or malformed ?path= (absolute, .md/.txt/.json/.jsonl)");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
let folded = false;
|
|
122
|
+
const doc = await reader.read(path, {}).catch((err) => {
|
|
123
|
+
if (foldReaderError(res, err)) {
|
|
124
|
+
folded = true;
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
112
127
|
throw err;
|
|
113
128
|
});
|
|
114
|
-
if (
|
|
129
|
+
if (folded)
|
|
130
|
+
return;
|
|
131
|
+
if (doc === null) {
|
|
132
|
+
sendError(res, 404, "not_found.shared_memory_document", "not found");
|
|
115
133
|
return;
|
|
116
|
-
const entries = Array.isArray(page) ? page : page.entries;
|
|
117
|
-
const nextCursor = Array.isArray(page) ? undefined : page.nextCursor;
|
|
118
|
-
sendJson(res, 200, { store: storeId, entries, ...(nextCursor !== undefined ? { nextCursor } : {}) });
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const path = query.get("path");
|
|
122
|
-
if (path === null || path === "" || !isDocumentPath(path)) {
|
|
123
|
-
sendError(res, 400, "request.query_invalid", "missing or malformed ?path= (absolute, .md/.txt/.json/.jsonl)");
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
let folded = false;
|
|
127
|
-
const doc = await reader.read(path, {}).catch((err) => {
|
|
128
|
-
if (foldReaderError(res, err)) {
|
|
129
|
-
folded = true;
|
|
130
|
-
return null;
|
|
131
134
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const bytes = Buffer.byteLength(doc.content, "utf8");
|
|
141
|
-
if (bytes > SHARED_MEMORY_READ_CAP_BYTES) {
|
|
142
|
-
sendError(res, 413, "request.payload_too_large", `document is ${bytes} bytes, over the ${SHARED_MEMORY_READ_CAP_BYTES}-byte shared-store read cap`, { bytes, capBytes: SHARED_MEMORY_READ_CAP_BYTES });
|
|
135
|
+
const bytes = Buffer.byteLength(doc.content, "utf8");
|
|
136
|
+
if (bytes > SHARED_MEMORY_READ_CAP_BYTES) {
|
|
137
|
+
sendError(res, 413, "request.payload_too_large", `document is ${bytes} bytes, over the ${SHARED_MEMORY_READ_CAP_BYTES}-byte shared-store read cap`, { bytes, capBytes: SHARED_MEMORY_READ_CAP_BYTES });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
sendJson(res, 200, { store: storeId, path, content: doc.content, bytes, ...(doc.updatedAt !== undefined ? { updatedAt: doc.updatedAt } : {}) });
|
|
143
141
|
return;
|
|
144
142
|
}
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
default:
|
|
144
|
+
return assertNever(match.decl.id);
|
|
147
145
|
}
|
|
148
|
-
miss.fell = true;
|
|
149
146
|
}
|
|
147
|
+
export const SHARED_MEMORY_ROUTES = [
|
|
148
|
+
{ id: "shared-memory-stores", path: "/v1/shared-memory/stores", methods: ["GET"] },
|
|
149
|
+
{ id: "shared-memory-store-documents", pattern: /^\/v1\/shared-memory\/stores\/(?<store>[^/]+)\/documents$/, label: "/v1/shared-memory/stores/:store/documents", methods: ["GET"] },
|
|
150
|
+
{ id: "shared-memory-store-document", pattern: /^\/v1\/shared-memory\/stores\/(?<store>[^/]+)\/document$/, label: "/v1/shared-memory/stores/:store/document", methods: ["GET"] },
|
|
151
|
+
];
|
|
150
152
|
//# sourceMappingURL=shared-memory.js.map
|
|
@@ -7,10 +7,19 @@
|
|
|
7
7
|
* 已含本路径,故 no-token 503 / draining / model-ready 三闸在进本域前就已生效。本域零可变状态。
|
|
8
8
|
*/
|
|
9
9
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
10
|
-
import type { RouteCtx } from "../route-ctx.js";
|
|
10
|
+
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
11
11
|
/** 表内=core 的前置校验形(客户端可改请求)⇒ 400;表外一律 500。 */
|
|
12
12
|
export declare function isCorePreflightValidationError(message: string): boolean;
|
|
13
13
|
/** 供跨仓锚门读:门要拿这张表去装机的 core 里逐条对账(见上方 doc)。 */
|
|
14
14
|
export declare const CORE_PREFLIGHT_VALIDATION_ANCHORS: readonly string[];
|
|
15
|
-
|
|
15
|
+
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。 */
|
|
16
|
+
export declare function handleSideQuery(req: IncomingMessage, res: ServerResponse, match: RouteMatch<SideQueryRouteId>, ctx: RouteCtx): Promise<void>;
|
|
17
|
+
export declare const SIDE_QUERY_ROUTES: readonly [{
|
|
18
|
+
readonly id: "side-query";
|
|
19
|
+
readonly path: "/v1/side-query";
|
|
20
|
+
readonly billable: true;
|
|
21
|
+
readonly methods: readonly ["POST"];
|
|
22
|
+
}];
|
|
23
|
+
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
24
|
+
export type SideQueryRouteId = RouteIdsOf<typeof SIDE_QUERY_ROUTES>;
|
|
16
25
|
//# sourceMappingURL=side-query.d.ts.map
|
|
@@ -4,6 +4,7 @@ import { isModelAllowlisted } from "../../model-select.js";
|
|
|
4
4
|
import { redactSecrets } from "../../trace/redact.js";
|
|
5
5
|
import { sendJson, sendError, httpErrorCode, msg } from "../send.js";
|
|
6
6
|
import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
7
|
+
import { assertNever } from "../route-ctx.js";
|
|
7
8
|
const CORE_PREFLIGHT_VALIDATION_PREFIXES = ["Unknown model ref ", "No model for role "];
|
|
8
9
|
export function isCorePreflightValidationError(message) {
|
|
9
10
|
return CORE_PREFLIGHT_VALIDATION_PREFIXES.some((prefix) => message.startsWith(prefix));
|
|
@@ -26,93 +27,94 @@ function isModelRole(value) {
|
|
|
26
27
|
function passthroughModelRole(value) {
|
|
27
28
|
return value;
|
|
28
29
|
}
|
|
29
|
-
export async function handleSideQuery(req, res,
|
|
30
|
-
const miss = { fell: false };
|
|
31
|
-
await handleSideQueryBody(req, res, url, ctx, miss);
|
|
32
|
-
return !miss.fell;
|
|
33
|
-
}
|
|
34
|
-
async function handleSideQueryBody(req, res, url, ctx, miss) {
|
|
30
|
+
export async function handleSideQuery(req, res, match, ctx) {
|
|
35
31
|
const { deps } = ctx;
|
|
36
32
|
const { readJson, rateLimited, quotaExceeded, leaseDenied } = ctx.helpers;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
|
|
43
|
-
return;
|
|
44
|
-
const principal = gatedPrincipal(req, deps.config);
|
|
45
|
-
if (deps.config.requirePrincipal && !principal) {
|
|
46
|
-
sendPrincipalRefusal(res, req, deps.config);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
let body;
|
|
33
|
+
switch (match.decl.id) {
|
|
34
|
+
case "side-query": {
|
|
35
|
+
const ac = new AbortController();
|
|
36
|
+
const onClose = () => ac.abort();
|
|
37
|
+
res.on("close", onClose);
|
|
50
38
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
catch (e) {
|
|
57
|
-
const he = e instanceof HttpError ? e : new HttpError(400, msg(e));
|
|
58
|
-
sendError(res, he.status, httpErrorCode(he.status, he.code), he.message);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (!Array.isArray(body.messages) || body.messages.length === 0) {
|
|
62
|
-
sendError(res, 400, "request.body_shape", "messages must be a non-empty array (SideQueryMessage[])");
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (body.thinking !== undefined && !isThinkingLevel(body.thinking)) {
|
|
66
|
-
sendError(res, 400, "request.field_invalid", "invalid thinking level");
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (typeof body.model !== "string" && body.modelRole !== undefined && !isModelRole(body.modelRole)) {
|
|
70
|
-
sendError(res, 400, "request.field_invalid", `invalid modelRole (expected one of: ${Object.keys(MODEL_ROLE_TABLE).join(", ")}) — a role only resolves a model when 'model' is absent`);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (typeof body.model === "string" && body.model.length > 0) {
|
|
74
|
-
const bare = deps.config.models ?? {};
|
|
75
|
-
const catalog = expandTiers(bare, deps.config.tiers ?? {}) ?? bare;
|
|
76
|
-
if (!isModelAllowlisted(body.model, catalog, deps.config.atModelAllowlist)) {
|
|
77
|
-
sendError(res, 400, "request.model_not_allowed", `model is not in this deployment's @-mention allowlist: "${body.model.slice(0, 120)}" — the allowlist is configured by the operator (config-center models.atModelAllowlist)`);
|
|
39
|
+
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
|
|
40
|
+
return;
|
|
41
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
42
|
+
if (deps.config.requirePrincipal && !principal) {
|
|
43
|
+
sendPrincipalRefusal(res, req, deps.config);
|
|
78
44
|
return;
|
|
79
45
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
46
|
+
let body;
|
|
47
|
+
try {
|
|
48
|
+
const raw = await readJson(req);
|
|
49
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
50
|
+
throw new HttpError(400, "body must be a JSON object (SideQuerySpec shape)");
|
|
51
|
+
body = raw;
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
const he = e instanceof HttpError ? e : new HttpError(400, msg(e));
|
|
55
|
+
sendError(res, he.status, httpErrorCode(he.status, he.code), he.message);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!Array.isArray(body.messages) || body.messages.length === 0) {
|
|
59
|
+
sendError(res, 400, "request.body_shape", "messages must be a non-empty array (SideQueryMessage[])");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (body.thinking !== undefined && !isThinkingLevel(body.thinking)) {
|
|
63
|
+
sendError(res, 400, "request.field_invalid", "invalid thinking level");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (typeof body.model !== "string" && body.modelRole !== undefined && !isModelRole(body.modelRole)) {
|
|
67
|
+
sendError(res, 400, "request.field_invalid", `invalid modelRole (expected one of: ${Object.keys(MODEL_ROLE_TABLE).join(", ")}) — a role only resolves a model when 'model' is absent`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (typeof body.model === "string" && body.model.length > 0) {
|
|
71
|
+
const bare = deps.config.models ?? {};
|
|
72
|
+
const catalog = expandTiers(bare, deps.config.tiers ?? {}) ?? bare;
|
|
73
|
+
if (!isModelAllowlisted(body.model, catalog, deps.config.atModelAllowlist)) {
|
|
74
|
+
sendError(res, 400, "request.model_not_allowed", `model is not in this deployment's @-mention allowlist: "${body.model.slice(0, 120)}" — the allowlist is configured by the operator (config-center models.atModelAllowlist)`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (ac.signal.aborted)
|
|
79
|
+
return;
|
|
80
|
+
const spec = {
|
|
81
|
+
messages: body.messages,
|
|
82
|
+
...(typeof body.model === "string" ? { model: body.model } : {}),
|
|
83
|
+
...(typeof body.modelRole === "string" ? { modelRole: passthroughModelRole(body.modelRole) } : {}),
|
|
84
|
+
...(isThinkingLevel(body.thinking) ? { thinking: body.thinking } : {}),
|
|
85
|
+
...(typeof body.systemPrompt === "string" ? { systemPrompt: body.systemPrompt } : {}),
|
|
86
|
+
...(Array.isArray(body.tools) ? { tools: body.tools } : {}),
|
|
87
|
+
...(typeof body.maxOutputTokens === "number" && Number.isFinite(body.maxOutputTokens) && body.maxOutputTokens > 0 ? { maxOutputTokens: Math.floor(body.maxOutputTokens) } : {}),
|
|
88
|
+
signal: ac.signal,
|
|
89
|
+
};
|
|
90
|
+
try {
|
|
91
|
+
const result = await deps.sideQuery(spec);
|
|
92
|
+
const rr = result;
|
|
93
|
+
const modelId = typeof rr.model === "string" ? rr.model : "unknown";
|
|
94
|
+
deps.sideQueryAccounting?.(principal, { model: modelId, usage: rr.usage });
|
|
95
|
+
deps.metrics?.inc?.("side_query_total", { result: "ok" });
|
|
96
|
+
if (!res.writableEnded)
|
|
97
|
+
sendJson(res, 200, result);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
deps.metrics?.inc?.("side_query_total", { result: "error" });
|
|
101
|
+
if (!res.writableEnded && !ac.signal.aborted) {
|
|
102
|
+
const m = msg(e);
|
|
103
|
+
const isValidation = isCorePreflightValidationError(m);
|
|
104
|
+
sendError(res, isValidation ? 400 : 500, isValidation ? "request.field_invalid" : "internal.error", redactSecrets(m));
|
|
105
|
+
}
|
|
108
106
|
}
|
|
109
107
|
}
|
|
108
|
+
finally {
|
|
109
|
+
res.off("close", onClose);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
110
112
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
113
|
+
default:
|
|
114
|
+
return assertNever(match.decl.id);
|
|
115
115
|
}
|
|
116
|
-
miss.fell = true;
|
|
117
116
|
}
|
|
117
|
+
export const SIDE_QUERY_ROUTES = [
|
|
118
|
+
{ id: "side-query", path: "/v1/side-query", billable: true, methods: ["POST"] },
|
|
119
|
+
];
|
|
118
120
|
//# sourceMappingURL=side-query.js.map
|
|
@@ -17,6 +17,21 @@
|
|
|
17
17
|
* 与 runs 域共用 `ctx.legs.prepareSpec`(读体/校验/建 spec 的同一条前段)。
|
|
18
18
|
*/
|
|
19
19
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
20
|
-
import type { RouteCtx } from "../route-ctx.js";
|
|
21
|
-
|
|
20
|
+
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
21
|
+
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(A9 的唯一改动:去缩进;B3 的唯一改动:
|
|
22
|
+
* 给两个匿名 `idemCache.run` 回调与三条驱动腿起名)。裸 `return;` = 「本域已应答」;走到函数尾才是「没匹配上」。 */
|
|
23
|
+
export declare function handleTasks(req: IncomingMessage, res: ServerResponse, match: RouteMatch<TasksRouteId>, ctx: RouteCtx): Promise<void>;
|
|
24
|
+
export declare const TASKS_ROUTES: readonly [{
|
|
25
|
+
readonly id: "tasks-submit";
|
|
26
|
+
readonly path: "/v1/tasks";
|
|
27
|
+
readonly billable: true;
|
|
28
|
+
readonly methods: readonly ["POST"];
|
|
29
|
+
}, {
|
|
30
|
+
readonly id: "tasks-submit-stream";
|
|
31
|
+
readonly path: "/v1/tasks/stream";
|
|
32
|
+
readonly billable: true;
|
|
33
|
+
readonly methods: readonly ["POST"];
|
|
34
|
+
}];
|
|
35
|
+
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
36
|
+
export type TasksRouteId = RouteIdsOf<typeof TASKS_ROUTES>;
|
|
22
37
|
//# sourceMappingURL=tasks.d.ts.map
|
|
@@ -23,6 +23,7 @@ import { createApprovalCardEmitter, resolveApprovalLeg, resolveStreamApprovalGat
|
|
|
23
23
|
import { buildActiveRunConflict, toDoneFrameResult } from "../active-run-conflict.js";
|
|
24
24
|
import { headerStr, gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
25
25
|
import { failOpenTagForDroppedFrame, recordFailOpen } from "../../observability/fail-open.js";
|
|
26
|
+
import { assertNever } from "../route-ctx.js";
|
|
26
27
|
const DISCONNECT_PARK_GRACE_MS = 10_000;
|
|
27
28
|
function sseData(res, payload) {
|
|
28
29
|
const anchor = payload.eventId;
|
|
@@ -39,12 +40,7 @@ function sendSubmitResult(res, resp) {
|
|
|
39
40
|
sendJson(res, resp.status, resp.body);
|
|
40
41
|
}
|
|
41
42
|
const MULTI_ATTEMPT_NO_EVENT_CHANNEL_NOTICE = "verify / cascade ran with their normal multi-attempt semantics, but the synchronous /v1/tasks endpoint has no event channel — no status/progress frames exist to replay on this shape. For observable events submit via POST /v1/runs and read GET /v1/runs/:id/events.";
|
|
42
|
-
export async function handleTasks(req, res,
|
|
43
|
-
const miss = { fell: false };
|
|
44
|
-
await handleTasksBody(req, res, url, ctx, miss);
|
|
45
|
-
return !miss.fell;
|
|
46
|
-
}
|
|
47
|
-
async function handleTasksBody(req, res, url, ctx, miss) {
|
|
43
|
+
export async function handleTasks(req, res, match, ctx) {
|
|
48
44
|
const { deps } = ctx;
|
|
49
45
|
const { idemCache, inflightRuns, cancelledViaVerb, steerableRuns, counters } = ctx.registry;
|
|
50
46
|
const { rateLimited, quotaExceeded, leaseDenied, usageWindowDenied } = ctx.helpers;
|
|
@@ -59,12 +55,20 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
59
55
|
if (plane.errorCode)
|
|
60
56
|
reqState.runErrorCode = plane.errorCode;
|
|
61
57
|
};
|
|
62
|
-
|
|
58
|
+
switch (match.decl.id) {
|
|
59
|
+
case "tasks-submit":
|
|
60
|
+
case "tasks-submit-stream":
|
|
61
|
+
break;
|
|
62
|
+
default:
|
|
63
|
+
return assertNever(match.decl.id);
|
|
64
|
+
}
|
|
65
|
+
const liveStream = match.decl.id === "tasks-submit-stream";
|
|
66
|
+
{
|
|
63
67
|
const rawIdem = headerStr(req.headers["idempotency-key"]);
|
|
64
68
|
const idemKey = rawIdem ? scopedIdempotencyKey(rawIdem, source, gatedPrincipal(req, deps.config)) : undefined;
|
|
65
69
|
const cached = idemKey ? idemCache.peek(idemKey) : undefined;
|
|
66
70
|
if (cached) {
|
|
67
|
-
if (
|
|
71
|
+
if (liveStream) {
|
|
68
72
|
sseHeaders(res);
|
|
69
73
|
const hb = setInterval(() => {
|
|
70
74
|
if (!res.writableEnded)
|
|
@@ -90,11 +94,11 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
90
94
|
}
|
|
91
95
|
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
|
|
92
96
|
return;
|
|
93
|
-
const prepared = await prepareSpec(req, res, undefined, undefined, { liveStream
|
|
97
|
+
const prepared = await prepareSpec(req, res, undefined, undefined, { liveStream });
|
|
94
98
|
if (!prepared)
|
|
95
99
|
return;
|
|
96
100
|
const principal = prepared.auth?.principal;
|
|
97
|
-
if (
|
|
101
|
+
if (liveStream) {
|
|
98
102
|
if (prepared.verify || prepared.cascade) {
|
|
99
103
|
sendError(res, 400, "request.field_conflict", "verify / cascade are not supported on /v1/tasks/stream (both are multi-attempt, not a single stream) — use /v1/tasks or /v1/runs");
|
|
100
104
|
return;
|
|
@@ -860,6 +864,9 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
860
864
|
}
|
|
861
865
|
return;
|
|
862
866
|
}
|
|
863
|
-
miss.fell = true;
|
|
864
867
|
}
|
|
868
|
+
export const TASKS_ROUTES = [
|
|
869
|
+
{ id: "tasks-submit", path: "/v1/tasks", billable: true, methods: ["POST"] },
|
|
870
|
+
{ id: "tasks-submit-stream", path: "/v1/tasks/stream", billable: true, methods: ["POST"] },
|
|
871
|
+
];
|
|
865
872
|
//# sourceMappingURL=tasks.js.map
|
|
@@ -9,6 +9,44 @@
|
|
|
9
9
|
* 因此本域在 `handle()` 里的位置就是契约的一部分:必须留在鉴权门上方,顺序不可与其他域对调。本域零可变状态。
|
|
10
10
|
*/
|
|
11
11
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
12
|
-
import type { RouteCtx } from "../route-ctx.js";
|
|
13
|
-
|
|
12
|
+
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
13
|
+
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
14
|
+
* = 「本域已应答」;走到函数尾才是「没匹配上」。跨域的东西一律在首行解构出来。 */
|
|
15
|
+
export declare function handleTraceUsage(req: IncomingMessage, res: ServerResponse, match: RouteMatch<TraceUsageRouteId>, ctx: RouteCtx): Promise<void>;
|
|
16
|
+
export declare const TRACE_USAGE_ROUTES: readonly [{
|
|
17
|
+
readonly id: "tasks-source-summary";
|
|
18
|
+
readonly path: "/v1/tasks/source-summary";
|
|
19
|
+
readonly methods: readonly ["GET"];
|
|
20
|
+
}, {
|
|
21
|
+
readonly id: "task-trace-list";
|
|
22
|
+
readonly path: "/v1/tasks";
|
|
23
|
+
readonly methods: readonly ["GET"];
|
|
24
|
+
}, {
|
|
25
|
+
readonly id: "task-tool-result";
|
|
26
|
+
readonly pattern: RegExp;
|
|
27
|
+
readonly label: "/v1/tasks/:id/tool-results/:ref";
|
|
28
|
+
readonly methods: readonly ["GET"];
|
|
29
|
+
}, {
|
|
30
|
+
readonly id: "task-trace-turns";
|
|
31
|
+
readonly pattern: RegExp;
|
|
32
|
+
readonly label: "/v1/tasks/:id/$sub";
|
|
33
|
+
readonly methods: readonly ["GET"];
|
|
34
|
+
}, {
|
|
35
|
+
readonly id: "task-trace-stream";
|
|
36
|
+
readonly pattern: RegExp;
|
|
37
|
+
readonly label: "/v1/tasks/:id/$sub";
|
|
38
|
+
readonly methods: readonly ["GET"];
|
|
39
|
+
}, {
|
|
40
|
+
readonly id: "task-trace-artifacts";
|
|
41
|
+
readonly pattern: RegExp;
|
|
42
|
+
readonly label: "/v1/tasks/:id/$sub";
|
|
43
|
+
readonly methods: readonly ["GET"];
|
|
44
|
+
}, {
|
|
45
|
+
readonly id: "usage-analytics";
|
|
46
|
+
readonly pattern: RegExp;
|
|
47
|
+
readonly label: "/v1/usage/$sub";
|
|
48
|
+
readonly methods: readonly ["GET"];
|
|
49
|
+
}];
|
|
50
|
+
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
51
|
+
export type TraceUsageRouteId = RouteIdsOf<typeof TRACE_USAGE_ROUTES>;
|
|
14
52
|
//# sourceMappingURL=trace-usage.d.ts.map
|