@noopolis/daimon 0.2.2 → 0.2.4
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/runtime/grokBrokerProxy.d.ts +8 -1
- package/dist/runtime/grokBrokerProxy.d.ts.map +1 -1
- package/dist/runtime/grokBrokerProxy.js +28 -10
- package/dist/runtime/grokBrokerProxyBodyShape.d.ts +24 -0
- package/dist/runtime/grokBrokerProxyBodyShape.d.ts.map +1 -0
- package/dist/runtime/grokBrokerProxyBodyShape.js +60 -0
- package/dist/runtime/grokBrokerProxyRequest.d.ts +29 -0
- package/dist/runtime/grokBrokerProxyRequest.d.ts.map +1 -1
- package/dist/runtime/grokBrokerProxyRequest.js +68 -13
- package/dist/runtime/grokBrokerTurnMeter.d.ts +7 -2
- package/dist/runtime/grokBrokerTurnMeter.d.ts.map +1 -1
- package/dist/runtime/grokBrokerTurnMeter.js +8 -4
- package/dist/runtime/grokEngineBrokerTurn.d.ts +2 -0
- package/dist/runtime/grokEngineBrokerTurn.d.ts.map +1 -1
- package/dist/runtime/grokEngineBrokerTurn.js +5 -2
- package/dist/runtime/grokEngineBrokerTurnEnd.d.ts +8 -2
- package/dist/runtime/grokEngineBrokerTurnEnd.d.ts.map +1 -1
- package/dist/runtime/grokEngineBrokerTurnEnd.js +13 -2
- package/dist/runtime/grokWorkerToolProcess.d.ts +34 -0
- package/dist/runtime/grokWorkerToolProcess.d.ts.map +1 -0
- package/dist/runtime/grokWorkerToolProcess.js +78 -0
- package/dist/runtime/productionAgentTools.js +1 -1
- package/package.json +1 -1
|
@@ -64,6 +64,13 @@ export declare function createGrokBrokerProxyHandler(authority: GrokBrokerCreden
|
|
|
64
64
|
*/
|
|
65
65
|
export declare class GrokBrokerProxyRefusal extends Error {
|
|
66
66
|
readonly reason: string;
|
|
67
|
-
|
|
67
|
+
readonly check?: string | undefined;
|
|
68
|
+
readonly detail?: string | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* `check` names which body-gate check failed and `detail` is the refused
|
|
71
|
+
* body's content-free shape ({@link grokBrokerProxyBodyShape}); both are for
|
|
72
|
+
* the broker's stderr line, and `check` also rides the 400 body.
|
|
73
|
+
*/
|
|
74
|
+
constructor(reason: string, check?: string | undefined, detail?: string | undefined);
|
|
68
75
|
}
|
|
69
76
|
//# sourceMappingURL=grokBrokerProxy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grokBrokerProxy.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAExF,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAIpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,OAAO,EAAgE,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACtI,OAAO,EAAE,+BAA+B,
|
|
1
|
+
{"version":3,"file":"grokBrokerProxy.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAExF,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAIpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,OAAO,EAAgE,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACtI,OAAO,EAAE,+BAA+B,EAAmC,MAAM,6BAA6B,CAAC;AAG/G,OAAO,EAA2E,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG7I,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAkC,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5G,2FAA2F;AAC3F,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC,CAAC,CAAC;AAEH,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IAAE,WAAW,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,qBAAqB,CAAC,CAAC,mBAAmB,EAAC,MAAM,GAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,YAAY,CAAC,mBAAmB,CAAC,EAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,OAAO,CAAC,IAAI,OAAO,CAAA;CAAE,CAAC,CAAC;AACjQ,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,+BAA+B,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC,CAAC;AAE3N;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,6BAA6B,EAAE,QAAQ,GAAE,kBAAoC,EACxF,MAAM,GAAE,qBAAwD,EAAE,UAAU,SAAS,EACrF,MAAM,CAAC,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,mBAAmB;;;;mCAoC5B,MAAM,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC;iCACpC,MAAM;yBACd,MAAM,QAAQ,mBAAmB;uBAMnC,MAAM;qBACR,eAAe,YAAY,cAAc;GA5B7D;AAED,iFAAiF;AACjF,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,6BAA6B,EAAE,QAAQ,GAAE,kBAAoC,EACxF,MAAM,GAAE,qBAAwD,EAAE,MAAM,CAAC,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,mBAAmB;;mCAc9F,MAAM,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC;iCACpC,MAAM;yBACd,MAAM,QAAQ,mBAAmB;uBAMnC,MAAM;qBACR,eAAe,YAAY,cAAc;EAI7D;AAED;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAMnC,QAAQ,CAAC,MAAM,EAAE,MAAM;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IALtF;;;;OAIG;gBACkB,MAAM,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,MAAM,YAAA,EAAW,MAAM,CAAC,EAAE,MAAM,YAAA;CACvF"}
|
|
@@ -6,7 +6,8 @@ import { CLI_ENGINE_MAX_DIAGNOSTIC_BYTES } from "../pi/cliChildOutput.js";
|
|
|
6
6
|
import { EngineBrokerCapabilities } from "./engineBrokerCapabilities.js";
|
|
7
7
|
import { ENGINE_BROKER_AUTH_STALE } from "./engineBrokerProtocol.js";
|
|
8
8
|
import { DEFAULT_GROK_BROKER_MODEL_POLICY, parseGrokBrokerModelPolicy } from "./grokBrokerModelPolicy.js";
|
|
9
|
-
import { authorizeGrokBrokerProxyRequest } from "./grokBrokerProxyRequest.js";
|
|
9
|
+
import { authorizeGrokBrokerProxyRequest, GrokBrokerProxyRequestRejection } from "./grokBrokerProxyRequest.js";
|
|
10
|
+
import { formatGrokBrokerProxyBodyShape, grokBrokerProxyBodyShape } from "./grokBrokerProxyBodyShape.js";
|
|
10
11
|
import { GROK_SESSION_TITLE_SINK_KEY } from "./grokBrokerWorkerConfig.js";
|
|
11
12
|
import { parseGrokFinalReply, parseGrokResponseToolNames, parseGrokUpstreamUsage } from "./grokBrokerTurnMeter.js";
|
|
12
13
|
import { GROK_ENGINE_BROKER } from "../contracts/runtimeContractManifest.js";
|
|
@@ -77,9 +78,18 @@ export function createGrokBrokerProxyHandler(authority, upstream = defaultUpstre
|
|
|
77
78
|
*/
|
|
78
79
|
export class GrokBrokerProxyRefusal extends Error {
|
|
79
80
|
reason;
|
|
80
|
-
|
|
81
|
+
check;
|
|
82
|
+
detail;
|
|
83
|
+
/**
|
|
84
|
+
* `check` names which body-gate check failed and `detail` is the refused
|
|
85
|
+
* body's content-free shape ({@link grokBrokerProxyBodyShape}); both are for
|
|
86
|
+
* the broker's stderr line, and `check` also rides the 400 body.
|
|
87
|
+
*/
|
|
88
|
+
constructor(reason, check, detail) {
|
|
81
89
|
super(`grok broker proxy refused: ${reason}`);
|
|
82
90
|
this.reason = reason;
|
|
91
|
+
this.check = check;
|
|
92
|
+
this.detail = detail;
|
|
83
93
|
}
|
|
84
94
|
}
|
|
85
95
|
async function serve(request, response, authority, upstream, capabilities, guards, turns, fallback, grants) {
|
|
@@ -137,7 +147,7 @@ async function serve(request, response, authority, upstream, capabilities, guard
|
|
|
137
147
|
token = "";
|
|
138
148
|
// The spend gate runs after the body is proven a real lean worker request
|
|
139
149
|
// (a refused session-title body never counts) and before any upstream call.
|
|
140
|
-
const admission = turn.meter.admit();
|
|
150
|
+
const admission = turn.meter.admit({ keepFinal: prepared.compaction === true });
|
|
141
151
|
if ("refused" in admission) {
|
|
142
152
|
response.writeHead(429, { "content-type": "application/json", "cache-control": "no-store" });
|
|
143
153
|
response.end(JSON.stringify({ error: "turn limit reached", limit: admission.refused }));
|
|
@@ -167,8 +177,9 @@ async function serve(request, response, authority, upstream, capabilities, guard
|
|
|
167
177
|
// A successful answer that called nothing and stopped is the turn's final
|
|
168
178
|
// reply: the broker ends a worker that finished but did not exit on it
|
|
169
179
|
// (`grokEngineBrokerTurnEnd.ts`), and reports it when the launcher refused
|
|
170
|
-
// the worker's stdout. Observation only, exactly like the tool names.
|
|
171
|
-
|
|
180
|
+
// the worker's stdout. Observation only, exactly like the tool names. A
|
|
181
|
+
// compaction summary also calls nothing and stops, and is never the reply.
|
|
182
|
+
settle?.(usageOrEstimate(result.body, result.headers["content-type"]), toolCallsOrNothing(result.body, result.headers["content-type"]), result.status === 200 && prepared.compaction !== true ? finalReplyOrNothing(result.body, result.headers["content-type"]) : undefined);
|
|
172
183
|
response.writeHead(result.status, { "content-type": result.headers["content-type"] ?? "application/json", "cache-control": "no-store" });
|
|
173
184
|
response.end(result.body);
|
|
174
185
|
}
|
|
@@ -191,7 +202,8 @@ async function serve(request, response, authority, upstream, capabilities, guard
|
|
|
191
202
|
// A named refusal is its own account; anything else used to reach the log as
|
|
192
203
|
// the bare word `broker_unavailable`, which names nothing — so it carries the
|
|
193
204
|
// fault's own class and message, and nothing else, beside it.
|
|
194
|
-
const named = refused ?
|
|
205
|
+
const named = !refused ? `${refusal} (${brokerFaultCause(error, secrets)})`
|
|
206
|
+
: error instanceof GrokBrokerProxyRefusal && error.check !== undefined ? `${refusal} check=${error.check}${error.detail === undefined ? "" : ` ${error.detail}`}` : refusal;
|
|
195
207
|
if (!titleSink && !expectedWorkerProbe(request))
|
|
196
208
|
process.stderr.write(`[grok-proxy] refused: ${named}\n`);
|
|
197
209
|
// Grok's own session-title call is refused by design, and keeps the transient
|
|
@@ -206,7 +218,7 @@ async function serve(request, response, authority, upstream, capabilities, guard
|
|
|
206
218
|
}
|
|
207
219
|
if (refused) {
|
|
208
220
|
response.writeHead(400, { "content-type": "application/json", "cache-control": "no-store" });
|
|
209
|
-
response.end(JSON.stringify({ error: "broker refused this request", reason: refusal }));
|
|
221
|
+
response.end(JSON.stringify({ error: "broker refused this request", reason: refusal, ...(error instanceof GrokBrokerProxyRefusal && error.check !== undefined ? { check: error.check } : {}) }));
|
|
210
222
|
return;
|
|
211
223
|
}
|
|
212
224
|
response.writeHead(503, { "content-type": "application/json", "cache-control": "no-store" });
|
|
@@ -307,13 +319,19 @@ const finalReplyOrNothing = (body, contentType) => {
|
|
|
307
319
|
return undefined;
|
|
308
320
|
}
|
|
309
321
|
};
|
|
310
|
-
/**
|
|
322
|
+
/**
|
|
323
|
+
* The body gate, refused non-retryably: a rejected body is a policy miss, never
|
|
324
|
+
* a transient fault. The refusal names the failed check and the body's
|
|
325
|
+
* content-free shape, because a bare `request_body_rejected` sent a live
|
|
326
|
+
* training pilot to a stub harness to learn which of a dozen checks fired.
|
|
327
|
+
*/
|
|
311
328
|
function authorizeRequestOrRefuse(...args) {
|
|
312
329
|
try {
|
|
313
330
|
return authorizeGrokBrokerProxyRequest(...args);
|
|
314
331
|
}
|
|
315
|
-
catch {
|
|
316
|
-
|
|
332
|
+
catch (error) {
|
|
333
|
+
const check = error instanceof GrokBrokerProxyRequestRejection ? error.check : "credential";
|
|
334
|
+
throw new GrokBrokerProxyRefusal("request_body_rejected", check, formatGrokBrokerProxyBodyShape(grokBrokerProxyBodyShape(args[0].body)));
|
|
317
335
|
}
|
|
318
336
|
}
|
|
319
337
|
async function readBody(request) { const chunks = []; let bytes = 0; for await (const chunk of request) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A content-free description of one refused worker body, for the broker's own
|
|
3
|
+
* stderr: byte length, top-level member names, offered tool names, message
|
|
4
|
+
* count and roles, and `tool_choice`. Never a message, a parameter schema, a
|
|
5
|
+
* value of any other member, a header or a token.
|
|
6
|
+
*
|
|
7
|
+
* Every name comes from a body the worker wrote, so each is admitted only as a
|
|
8
|
+
* plain short identifier (anything else is `<invalid>`) and every list is
|
|
9
|
+
* bounded with a `<truncated>` last entry. A body that is not a JSON object
|
|
10
|
+
* reports its byte length alone.
|
|
11
|
+
*/
|
|
12
|
+
export type GrokBrokerProxyBodyShape = Readonly<{
|
|
13
|
+
bytes: number;
|
|
14
|
+
keys?: readonly string[];
|
|
15
|
+
tools?: readonly string[] | "absent" | "invalid";
|
|
16
|
+
messages?: number | "invalid";
|
|
17
|
+
roles?: readonly string[];
|
|
18
|
+
tool_choice?: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const GROK_PROXY_BODY_SHAPE_LIST_MAX = 32;
|
|
21
|
+
export declare function grokBrokerProxyBodyShape(body: Uint8Array): GrokBrokerProxyBodyShape;
|
|
22
|
+
/** One line, `key=value` pairs, for the `[grok-proxy] refused:` log. */
|
|
23
|
+
export declare const formatGrokBrokerProxyBodyShape: (shape: GrokBrokerProxyBodyShape) => string;
|
|
24
|
+
//# sourceMappingURL=grokBrokerProxyBodyShape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grokBrokerProxyBodyShape.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxyBodyShape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC,CAAC;AAGH,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAqBjD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,UAAU,GAAG,wBAAwB,CAmBnF;AAED,wEAAwE;AACxE,eAAO,MAAM,8BAA8B,GAAI,OAAO,wBAAwB,KAAG,MAOtE,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const NAME = /^[A-Za-z_][A-Za-z0-9_.-]{0,63}$/u;
|
|
2
|
+
export const GROK_PROXY_BODY_SHAPE_LIST_MAX = 32;
|
|
3
|
+
const safeName = (value) => typeof value === "string" && NAME.test(value) ? value : "<invalid>";
|
|
4
|
+
const bounded = (values) => values.length <= GROK_PROXY_BODY_SHAPE_LIST_MAX ? values : [...values.slice(0, GROK_PROXY_BODY_SHAPE_LIST_MAX - 1), "<truncated>"];
|
|
5
|
+
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
/** Roles in order, with consecutive repeats collapsed to `role*n` so a long history stays short. */
|
|
7
|
+
const roleRuns = (messages) => {
|
|
8
|
+
const runs = [];
|
|
9
|
+
let last;
|
|
10
|
+
let count = 0;
|
|
11
|
+
const flush = () => { if (last !== undefined)
|
|
12
|
+
runs.push(count > 1 ? `${last}*${count}` : last); };
|
|
13
|
+
for (const message of messages) {
|
|
14
|
+
const role = isObject(message) ? safeName(message.role) : "<invalid>";
|
|
15
|
+
if (role === last) {
|
|
16
|
+
count++;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
flush();
|
|
20
|
+
last = role;
|
|
21
|
+
count = 1;
|
|
22
|
+
}
|
|
23
|
+
flush();
|
|
24
|
+
return bounded(runs);
|
|
25
|
+
};
|
|
26
|
+
export function grokBrokerProxyBodyShape(body) {
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: false }).decode(body));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return { bytes: body.byteLength };
|
|
33
|
+
}
|
|
34
|
+
if (!isObject(parsed))
|
|
35
|
+
return { bytes: body.byteLength };
|
|
36
|
+
const tools = parsed.tools === undefined ? "absent" : Array.isArray(parsed.tools)
|
|
37
|
+
? bounded(parsed.tools.map((tool) => isObject(tool) && isObject(tool.function) ? safeName(tool.function.name) : "<invalid>"))
|
|
38
|
+
: "invalid";
|
|
39
|
+
const choice = parsed.tool_choice;
|
|
40
|
+
return {
|
|
41
|
+
bytes: body.byteLength,
|
|
42
|
+
keys: bounded(Object.keys(parsed).map(safeName).sort()),
|
|
43
|
+
tools,
|
|
44
|
+
messages: Array.isArray(parsed.messages) ? parsed.messages.length : "invalid",
|
|
45
|
+
...(Array.isArray(parsed.messages) ? { roles: roleRuns(parsed.messages) } : {}),
|
|
46
|
+
...(choice === undefined ? {} : {
|
|
47
|
+
tool_choice: typeof choice === "string" ? safeName(choice)
|
|
48
|
+
: isObject(choice) ? `object:${isObject(choice.function) ? safeName(choice.function.name) : safeName(choice.type)}` : "<invalid>"
|
|
49
|
+
})
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** One line, `key=value` pairs, for the `[grok-proxy] refused:` log. */
|
|
53
|
+
export const formatGrokBrokerProxyBodyShape = (shape) => [
|
|
54
|
+
`bytes=${shape.bytes}`,
|
|
55
|
+
...(shape.keys === undefined ? [] : [`keys=${shape.keys.join(",")}`]),
|
|
56
|
+
...(shape.tools === undefined ? [] : [`tools=${Array.isArray(shape.tools) ? shape.tools.join(",") : shape.tools}`]),
|
|
57
|
+
...(shape.messages === undefined ? [] : [`messages=${shape.messages}`]),
|
|
58
|
+
...(shape.roles === undefined ? [] : [`roles=${shape.roles.join(",")}`]),
|
|
59
|
+
...(shape.tool_choice === undefined ? [] : [`tool_choice=${shape.tool_choice}`])
|
|
60
|
+
].join(" ");
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { EngineBrokerCapabilities } from "./engineBrokerCapabilities.js";
|
|
2
2
|
import { type GrokBrokerModelPolicy } from "./grokBrokerModelPolicy.js";
|
|
3
|
+
/**
|
|
4
|
+
* Which body-gate check refused a request: a closed, content-free code, so a
|
|
5
|
+
* refusal on the broker's stderr names its cause without a stub harness.
|
|
6
|
+
*/
|
|
7
|
+
export type GrokBrokerProxyRejectionCheck = "route" | "body_size" | "bearer" | "scope" | "client_version" | "json" | "envelope" | "model" | "reasoning_effort" | "tools" | "members" | "stream_options";
|
|
8
|
+
/** A body-gate refusal. The message is the historic one; `check` names the failed check. */
|
|
9
|
+
export declare class GrokBrokerProxyRequestRejection extends Error {
|
|
10
|
+
readonly check: GrokBrokerProxyRejectionCheck;
|
|
11
|
+
constructor(check: GrokBrokerProxyRejectionCheck);
|
|
12
|
+
}
|
|
3
13
|
export type GrokBrokerProxyInput = Readonly<{
|
|
4
14
|
method: string;
|
|
5
15
|
pathname: string;
|
|
@@ -8,10 +18,12 @@ export type GrokBrokerProxyInput = Readonly<{
|
|
|
8
18
|
agentId?: string;
|
|
9
19
|
turnId?: string;
|
|
10
20
|
}>;
|
|
21
|
+
/** `compaction` marks a normalized auto-compaction summary request: its answer is a summary, never the turn's final reply. */
|
|
11
22
|
export type GrokBrokerUpstreamRequest = Readonly<{
|
|
12
23
|
url: "https://cli-chat-proxy.grok.com/v1/chat/completions";
|
|
13
24
|
headers: Readonly<Record<string, string>>;
|
|
14
25
|
body: Uint8Array;
|
|
26
|
+
compaction?: true;
|
|
15
27
|
}>;
|
|
16
28
|
/**
|
|
17
29
|
* Authorizes one worker request and rebuilds it for the provider.
|
|
@@ -24,7 +36,24 @@ export type GrokBrokerUpstreamRequest = Readonly<{
|
|
|
24
36
|
* - the offered tool names are exactly the lean visible set. Grok 1.0.34 turns
|
|
25
37
|
* an unmappable `--tools` entry into its full 19-tool set, and its per-turn
|
|
26
38
|
* `session_title` request carries a single forced tool; both are refused.
|
|
39
|
+
*
|
|
40
|
+
* One other shape is admitted, and only by normalizing it into the lean one:
|
|
41
|
+
* Grok 1.0.34's auto-compaction summary request ({@link normalizeGrokCompactionRequest}).
|
|
27
42
|
*/
|
|
28
43
|
export declare function authorizeGrokBrokerProxyRequest(input: GrokBrokerProxyInput, capabilities: EngineBrokerCapabilities, bearer: string, policy?: GrokBrokerModelPolicy): GrokBrokerUpstreamRequest;
|
|
44
|
+
/**
|
|
45
|
+
* Rewrites, in place, exactly the captured compaction shape into the lean
|
|
46
|
+
* worker shape, and reports whether it did. The two extra members are each
|
|
47
|
+
* the provider's default value (`tool_choice: "auto"` with tools offered,
|
|
48
|
+
* `temperature: 1`), so removing them changes nothing the model is asked; the
|
|
49
|
+
* missing effort is set to the declared one, so the request cannot fall back
|
|
50
|
+
* to a provider default effort. Every other check — model, tools, stream
|
|
51
|
+
* options — then runs on the result exactly as for any worker request, and
|
|
52
|
+
* the forwarded body is indistinguishable from an ordinary lean request: the
|
|
53
|
+
* spend envelope is unchanged. Any other value of either member (a forced
|
|
54
|
+
* `tool_choice`, another temperature) or any other member set is left as is,
|
|
55
|
+
* and refused by the ordinary checks.
|
|
56
|
+
*/
|
|
57
|
+
export declare function normalizeGrokCompactionRequest(parsed: Record<string, unknown>, reasoningEffort: string): boolean;
|
|
29
58
|
export declare function exactLeanTools(tools: unknown): boolean;
|
|
30
59
|
//# sourceMappingURL=grokBrokerProxyRequest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grokBrokerProxyRequest.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxyRequest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EAAgE,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"grokBrokerProxyRequest.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxyRequest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EAAgE,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAItI;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,gBAAgB,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAExM,4FAA4F;AAC5F,qBAAa,+BAAgC,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,6BAA6B;gBAApC,KAAK,EAAE,6BAA6B;CAC1D;AAED,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC9L,8HAA8H;AAC9H,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,qDAAqD,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAEjM;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,qBAAwD,GAAG,yBAAyB,CAwChO;AAcD;;;;;;;;;;;;GAYG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAOhH;AASD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAStD"}
|
|
@@ -2,6 +2,15 @@ import { GROK_ENGINE_BROKER } from "../contracts/runtimeContractManifest.js";
|
|
|
2
2
|
import { GROK_WORKER_VISIBLE_TOOLS } from "../contracts/grokWorkerContract.js";
|
|
3
3
|
import { DEFAULT_GROK_BROKER_MODEL_POLICY, parseGrokBrokerModelPolicy } from "./grokBrokerModelPolicy.js";
|
|
4
4
|
const MAX_BODY = 2 * 1024 * 1024;
|
|
5
|
+
/** A body-gate refusal. The message is the historic one; `check` names the failed check. */
|
|
6
|
+
export class GrokBrokerProxyRequestRejection extends Error {
|
|
7
|
+
check;
|
|
8
|
+
constructor(check) {
|
|
9
|
+
super("broker proxy request rejected");
|
|
10
|
+
this.check = check;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function reject(check) { throw new GrokBrokerProxyRequestRejection(check); }
|
|
5
14
|
/**
|
|
6
15
|
* Authorizes one worker request and rebuilds it for the provider.
|
|
7
16
|
*
|
|
@@ -13,23 +22,28 @@ const MAX_BODY = 2 * 1024 * 1024;
|
|
|
13
22
|
* - the offered tool names are exactly the lean visible set. Grok 1.0.34 turns
|
|
14
23
|
* an unmappable `--tools` entry into its full 19-tool set, and its per-turn
|
|
15
24
|
* `session_title` request carries a single forced tool; both are refused.
|
|
25
|
+
*
|
|
26
|
+
* One other shape is admitted, and only by normalizing it into the lean one:
|
|
27
|
+
* Grok 1.0.34's auto-compaction summary request ({@link normalizeGrokCompactionRequest}).
|
|
16
28
|
*/
|
|
17
29
|
export function authorizeGrokBrokerProxyRequest(input, capabilities, bearer, policy = DEFAULT_GROK_BROKER_MODEL_POLICY) {
|
|
18
30
|
const declared = parseGrokBrokerModelPolicy(policy);
|
|
19
|
-
if (input.method !== "POST" || input.pathname !== "/v1/chat/completions"
|
|
20
|
-
|
|
31
|
+
if (input.method !== "POST" || input.pathname !== "/v1/chat/completions")
|
|
32
|
+
reject("route");
|
|
33
|
+
if (input.body.byteLength < 2 || input.body.byteLength > MAX_BODY)
|
|
34
|
+
reject("body_size");
|
|
21
35
|
const authorization = input.headers.authorization;
|
|
22
36
|
const match = authorization?.match(/^Bearer ([A-Za-z0-9_-]{40,})$/u);
|
|
23
37
|
if (match === null || match === undefined)
|
|
24
|
-
|
|
38
|
+
return reject("bearer");
|
|
25
39
|
const scope = capabilities.authorizeToken(match[1]);
|
|
26
40
|
if (scope === undefined || (input.agentId !== undefined && scope.agentId !== input.agentId) || (input.turnId !== undefined && scope.turnId !== input.turnId))
|
|
27
|
-
|
|
41
|
+
reject("scope");
|
|
28
42
|
if (!bearer || /[\r\n]/u.test(bearer))
|
|
29
43
|
throw new Error("broker credential authority unavailable");
|
|
30
44
|
const clientVersion = input.headers["x-grok-client-version"];
|
|
31
45
|
if (clientVersion !== GROK_ENGINE_BROKER.grokCliVersion)
|
|
32
|
-
|
|
46
|
+
reject("client_version");
|
|
33
47
|
let parsed;
|
|
34
48
|
// `__proto__` is refused at any depth: JSON.parse makes it an ordinary own
|
|
35
49
|
// member, but an upstream JavaScript parser may treat it as a prototype.
|
|
@@ -41,16 +55,21 @@ export function authorizeGrokBrokerProxyRequest(input, capabilities, bearer, pol
|
|
|
41
55
|
});
|
|
42
56
|
}
|
|
43
57
|
catch {
|
|
44
|
-
|
|
58
|
+
return reject("json");
|
|
45
59
|
}
|
|
46
60
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || parsed.stream !== true || !Array.isArray(parsed.messages))
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
reject("envelope");
|
|
62
|
+
const compaction = normalizeGrokCompactionRequest(parsed, declared.reasoningEffort);
|
|
63
|
+
if (parsed.model !== declared.model)
|
|
64
|
+
reject("model");
|
|
65
|
+
if (parsed.reasoning_effort !== declared.reasoningEffort)
|
|
66
|
+
reject("reasoning_effort");
|
|
50
67
|
if (!exactLeanTools(parsed.tools))
|
|
51
|
-
|
|
52
|
-
if (Object.keys(parsed).some((key) => !LEAN_BODY_MEMBERS.has(key))
|
|
53
|
-
|
|
68
|
+
reject("tools");
|
|
69
|
+
if (Object.keys(parsed).some((key) => !LEAN_BODY_MEMBERS.has(key)))
|
|
70
|
+
reject("members");
|
|
71
|
+
if (!validStreamOptions(parsed.stream_options))
|
|
72
|
+
reject("stream_options");
|
|
54
73
|
// Keep origin/main's production handling: UTF-8 replacement decoding, then
|
|
55
74
|
// serialize the validated object (including last-member-wins duplicates).
|
|
56
75
|
// Only a clocked turn takes the stricter date rewrite before this gate.
|
|
@@ -61,9 +80,45 @@ export function authorizeGrokBrokerProxyRequest(input, capabilities, bearer, pol
|
|
|
61
80
|
"x-xai-token-auth": "xai-grok-cli", "x-grok-model-override": declared.model,
|
|
62
81
|
"x-grok-client-version": clientVersion, "x-grok-client-identifier": "grok-shell"
|
|
63
82
|
},
|
|
64
|
-
body: Buffer.from(JSON.stringify(parsed))
|
|
83
|
+
body: Buffer.from(JSON.stringify(parsed)),
|
|
84
|
+
...(compaction ? { compaction: true } : {})
|
|
65
85
|
};
|
|
66
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Top-level members of Grok 1.0.34's auto-compaction summary request (live stub
|
|
89
|
+
* capture against the pinned binary in the training image, 2026-10-21). When a
|
|
90
|
+
* session nears the model's `context_window`, Grok asks the same model for a
|
|
91
|
+
* summary — single-pass, or a second "two-pass" request — and sends it with
|
|
92
|
+
* the lean tool set, `tool_choice: "auto"` and `temperature: 1`, and *without*
|
|
93
|
+
* `reasoning_effort`. Refusing it does not stop the session: Grok gives up on
|
|
94
|
+
* compaction and carries the whole history forward, so every refusal was a
|
|
95
|
+
* wasted round trip and the context kept growing.
|
|
96
|
+
*/
|
|
97
|
+
const COMPACTION_BODY_MEMBERS = new Set(["messages", "model", "stream", "stream_options", "temperature", "tool_choice", "tools"]);
|
|
98
|
+
/**
|
|
99
|
+
* Rewrites, in place, exactly the captured compaction shape into the lean
|
|
100
|
+
* worker shape, and reports whether it did. The two extra members are each
|
|
101
|
+
* the provider's default value (`tool_choice: "auto"` with tools offered,
|
|
102
|
+
* `temperature: 1`), so removing them changes nothing the model is asked; the
|
|
103
|
+
* missing effort is set to the declared one, so the request cannot fall back
|
|
104
|
+
* to a provider default effort. Every other check — model, tools, stream
|
|
105
|
+
* options — then runs on the result exactly as for any worker request, and
|
|
106
|
+
* the forwarded body is indistinguishable from an ordinary lean request: the
|
|
107
|
+
* spend envelope is unchanged. Any other value of either member (a forced
|
|
108
|
+
* `tool_choice`, another temperature) or any other member set is left as is,
|
|
109
|
+
* and refused by the ordinary checks.
|
|
110
|
+
*/
|
|
111
|
+
export function normalizeGrokCompactionRequest(parsed, reasoningEffort) {
|
|
112
|
+
const keys = Object.keys(parsed);
|
|
113
|
+
if (keys.length !== COMPACTION_BODY_MEMBERS.size || keys.some((key) => !COMPACTION_BODY_MEMBERS.has(key)))
|
|
114
|
+
return false;
|
|
115
|
+
if (parsed.temperature !== 1 || parsed.tool_choice !== "auto")
|
|
116
|
+
return false;
|
|
117
|
+
delete parsed.temperature;
|
|
118
|
+
delete parsed.tool_choice;
|
|
119
|
+
parsed.reasoning_effort = reasoningEffort;
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
67
122
|
/** Top-level members of a Grok 1.0.34 lean worker chat-completions body (live stub capture). */
|
|
68
123
|
const LEAN_BODY_MEMBERS = new Set(["messages", "model", "reasoning_effort", "stream", "stream_options", "tools"]);
|
|
69
124
|
/** Members of each lean tool `function` entry (live stub capture). */
|
|
@@ -83,8 +83,13 @@ export declare class GrokBrokerTurnMeter {
|
|
|
83
83
|
private final;
|
|
84
84
|
private activityAt;
|
|
85
85
|
constructor(limits: EngineBrokerTurnLimits, onLimit?: (reason: Exclude<EngineBrokerLimitReason, "none">) => void, now?: () => number);
|
|
86
|
-
/**
|
|
87
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Returns the request index and its upstream abort signal when admitted, the limit that refused it, or `busy` while another request is in flight.
|
|
88
|
+
* `keepFinal` admits a request that cannot replace the answer (a compaction summary), so a final reply already recorded survives it.
|
|
89
|
+
*/
|
|
90
|
+
admit(options?: Readonly<{
|
|
91
|
+
keepFinal?: boolean;
|
|
92
|
+
}>): Readonly<{
|
|
88
93
|
index: number;
|
|
89
94
|
signal: AbortSignal;
|
|
90
95
|
} | {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grokBrokerTurnMeter.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerTurnMeter.ts"],"names":[],"mappings":"AACA,OAAO,EAA4B,KAAK,uBAAuB,EAAE,KAAK,sBAAsB,EAAE,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAElK;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,qBAAqB,CAAC;IAAC,SAAS,CAAC,EAAE,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AACxK,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,uBAAuB,CAAC;IAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAA;CAAE,CAAC,CAAC;AAC5O,wHAAwH;AACxH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEzF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,OAAO,CAAC;AACnD,yGAAyG;AACzG,eAAO,MAAM,iBAAiB,GAAI,OAAO,qBAAqB,KAAG,MAC4B,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,mBAAmB;IAQlB,QAAQ,CAAC,MAAM,EAAE,sBAAsB;IAAE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAgF,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPjL,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiI;IACzJ,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,QAAQ,CAA6D;IAC7E,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,UAAU,CAAS;gBACN,MAAM,EAAE,sBAAsB,EAAmB,OAAO,GAAE,CAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,EAAE,MAAM,CAAC,KAAK,IAAsB,EAAmB,GAAG,GAAE,MAAM,MAAiB;IAK1M
|
|
1
|
+
{"version":3,"file":"grokBrokerTurnMeter.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerTurnMeter.ts"],"names":[],"mappings":"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"}
|
|
@@ -63,8 +63,11 @@ export class GrokBrokerTurnMeter {
|
|
|
63
63
|
this.startedAt = now();
|
|
64
64
|
this.activityAt = this.startedAt;
|
|
65
65
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Returns the request index and its upstream abort signal when admitted, the limit that refused it, or `busy` while another request is in flight.
|
|
68
|
+
* `keepFinal` admits a request that cannot replace the answer (a compaction summary), so a final reply already recorded survives it.
|
|
69
|
+
*/
|
|
70
|
+
admit(options = {}) {
|
|
68
71
|
if (this.reason === "none") {
|
|
69
72
|
if (this.now() - this.startedAt >= this.limits.timeoutMs)
|
|
70
73
|
this.trip("timeout");
|
|
@@ -78,8 +81,9 @@ export class GrokBrokerTurnMeter {
|
|
|
78
81
|
if (this.inFlight !== undefined)
|
|
79
82
|
return { busy: true };
|
|
80
83
|
this.timings.push({ startedAt: new Date(this.now()).toISOString() });
|
|
81
|
-
// A new request means the previous answer was not the last one.
|
|
82
|
-
|
|
84
|
+
// A new sampling request means the previous answer was not the last one.
|
|
85
|
+
if (options.keepFinal !== true)
|
|
86
|
+
this.final = undefined;
|
|
83
87
|
this.activityAt = this.now();
|
|
84
88
|
const controller = new AbortController();
|
|
85
89
|
this.inFlight = { index: this.timings.length - 1, controller };
|
|
@@ -58,6 +58,8 @@ export type GrokEngineBrokerTurnDependencies = Readonly<{
|
|
|
58
58
|
runNative(input: NativeBrokerTurn, signal: AbortSignal): Promise<Readonly<NativeBrokerTurnResult>>;
|
|
59
59
|
/** The worker the launcher started for this identity (`/proc` by default); only read when the broker ends a turn itself. */
|
|
60
60
|
observeWorker?(workerUid: number): Promise<GrokWorkerProcess | undefined>;
|
|
61
|
+
/** Whether the worker identity has a live local tool process (default: `grokWorkerToolProcessLive` over `/proc`). */
|
|
62
|
+
observeToolProcess?(workerUid: number): Promise<boolean>;
|
|
61
63
|
/** Overrides the turn-end grace and idle bounds (tests). */
|
|
62
64
|
turnEnd?: GrokBrokerTurnEndTiming;
|
|
63
65
|
/** Whether no live process holds the worker identity (`/proc` by default); polled, bounded, before an aborted turn is sealed. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grokEngineBrokerTurn.d.ts","sourceRoot":"","sources":["../../src/runtime/grokEngineBrokerTurn.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,EAEL,oBAAoB,EAAE,KAAK,gBAAgB,EAAE,KAAK,sBAAsB,EACzE,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAA2B,KAAK,kCAAkC,EAAE,MAAM,8BAA8B,CAAC;AAMhH,OAAO,EAEL,KAAK,8BAA8B,EAAE,KAAK,uBAAuB,EAClE,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAAqD,KAAK,0BAA0B,EAAE,KAAK,8BAA8B,EAA8B,MAAM,iCAAiC,CAAC;AACtM,OAAO,EAA2B,KAAK,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACzJ,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EAAmE,KAAK,+BAA+B,EAAE,MAAM,gCAAgC,CAAC;AAGvJ,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,OAAO,EAAgK,KAAK,uBAAuB,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAClQ,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"grokEngineBrokerTurn.d.ts","sourceRoot":"","sources":["../../src/runtime/grokEngineBrokerTurn.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ import { finishBrokerTurnWithUsage } from "./grokEngineBrokerMetering.js";
|
|
|
14
14
|
import { GrokBrokerTurnMeter } from "./grokBrokerTurnMeter.js";
|
|
15
15
|
import { awaitGrokWorkerIdentityEmpty, findGrokWorkerProcess, GROK_WORKER_REAP_WAIT_MS, grokWorkerIdentityEmpty, watchGrokBrokerTurnEnd } from "./grokEngineBrokerTurnEnd.js";
|
|
16
16
|
import { GrokWorkerAttestationFailure } from "./grokWorkerAttestation.js";
|
|
17
|
+
import { grokWorkerToolProcessLive, grokWorkerToolProcessSampler } from "./grokWorkerToolProcess.js";
|
|
17
18
|
export class EngineBrokerTurnFailure extends Error {
|
|
18
19
|
code;
|
|
19
20
|
diagnostic;
|
|
@@ -119,12 +120,14 @@ export async function runGrokEngineBrokerTurn(deps, registration, wakeId, prompt
|
|
|
119
120
|
deps.proxy.registerTurn(turnId, { policy: registration.model, meter, clock });
|
|
120
121
|
const capabilityTtlMs = engineBrokerCapabilityTtlMs(limits.timeoutMs);
|
|
121
122
|
const providerCapability = deps.proxy.capabilities.issue(agentId, turnId, capabilityTtlMs), mcpCapability = deps.mcp.register(agentId, turnId, mcpEndpoint, capabilityTtlMs);
|
|
123
|
+
// A shell command Grok runs itself is work the proxy and facade never see.
|
|
124
|
+
const toolProcess = grokWorkerToolProcessSampler(() => (deps.observeToolProcess ?? grokWorkerToolProcessLive)(registration.workerUid));
|
|
122
125
|
watchdog = watchGrokBrokerTurnEnd({
|
|
123
|
-
meter, activity: () => deps.mcp.activity?.(turnId), signal: controller.signal, ...(deps.turnEnd === undefined ? {} : { timing: deps.turnEnd }),
|
|
126
|
+
meter, activity: () => deps.mcp.activity?.(turnId), toolProcess: toolProcess.live, signal: controller.signal, ...(deps.turnEnd === undefined ? {} : { timing: deps.turnEnd }),
|
|
124
127
|
// The worker is read once, as soon as it has made a request — long before
|
|
125
128
|
// anything can end the turn and reap it — so a final reply is never left
|
|
126
129
|
// without the worker that gave it.
|
|
127
|
-
onPoll: () => { if (worker === undefined && meter.snapshot().requests > 0)
|
|
130
|
+
onPoll: () => { toolProcess.poll(); if (worker === undefined && meter.snapshot().requests > 0)
|
|
128
131
|
worker = (deps.observeWorker ?? findGrokWorkerProcess)(registration.workerUid).catch(() => undefined); },
|
|
129
132
|
onFinalReply: () => { worker ??= (deps.observeWorker ?? findGrokWorkerProcess)(registration.workerUid).catch(() => undefined); },
|
|
130
133
|
// An idle stall is a deadline of its own: it trips the meter as a timeout, so it seals like one.
|
|
@@ -30,8 +30,12 @@ import type { GrokBrokerTurnMeter } from "./grokBrokerTurnMeter.js";
|
|
|
30
30
|
* so a model that keeps going is never cut off. Independently, a turn with no
|
|
31
31
|
* model request and no tool call in flight and no activity of either kind for
|
|
32
32
|
* `idleMs` ends as `onEnd("idle")` — the backstop for a worker that stalls
|
|
33
|
-
* without ever answering. Shell tools Grok runs itself
|
|
34
|
-
* the
|
|
33
|
+
* without ever answering. Shell tools Grok runs itself never reach the proxy
|
|
34
|
+
* or the facade, and Grok lets the model give one a timeout of up to ten
|
|
35
|
+
* hours, so a worker with a live tool process (`toolProcess`,
|
|
36
|
+
* `grokWorkerToolProcess.ts`) is working too: it is never idle while one runs,
|
|
37
|
+
* and the quiet clock restarts from the last poll that saw one. The idle bound
|
|
38
|
+
* stays minutes, not seconds, because that observation is a poll.
|
|
35
39
|
*/
|
|
36
40
|
export type GrokBrokerTurnEndTiming = Readonly<{
|
|
37
41
|
finalGraceMs: number;
|
|
@@ -45,6 +49,8 @@ export type GrokBrokerTurnEndReason = "final_reply" | "idle";
|
|
|
45
49
|
export declare function watchGrokBrokerTurnEnd(input: Readonly<{
|
|
46
50
|
meter: Pick<GrokBrokerTurnMeter, "finalReply" | "busy" | "lastActivityAt">;
|
|
47
51
|
activity: () => EngineBrokerMcpActivity | undefined;
|
|
52
|
+
/** Whether the worker has a local tool process alive, as last observed; absent means never observed. */
|
|
53
|
+
toolProcess?: () => boolean;
|
|
48
54
|
onFinalReply: () => void;
|
|
49
55
|
/** Every poll, before any decision: the broker records the worker while it is certainly alive. */
|
|
50
56
|
onPoll?: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grokEngineBrokerTurnEnd.d.ts","sourceRoot":"","sources":["../../src/runtime/grokEngineBrokerTurnEnd.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE
|
|
1
|
+
{"version":3,"file":"grokEngineBrokerTurnEnd.d.ts","sourceRoot":"","sources":["../../src/runtime/grokEngineBrokerTurnEnd.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACzG,eAAO,MAAM,oBAAoB,EAAE,uBAAqG,CAAC;AACzI,qGAAqG;AACrG,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG,MAAM,CAAC;AAE7D,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,CAAC;IACrD,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,YAAY,GAAG,MAAM,GAAG,gBAAgB,CAAC,CAAC;IAC3E,QAAQ,EAAE,MAAM,uBAAuB,GAAG,SAAS,CAAC;IACpD,wGAAwG;IACxG,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;IAC5B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,kGAAkG;IAClG,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACjD,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC,GAAG,QAAQ,CAAC;IAAE,IAAI,IAAI,IAAI,CAAA;CAAE,CAAC,CA8B9B;AAED,oJAAoJ;AACpJ,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAU,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAqBnH;AAED,2DAA2D;AAC3D,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAU,EAAE,eAAe,UAAQ,GACvD,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAA2B,EAAE,MAAM,SAAM,EAAE,QAAQ,SAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAarM"}
|
|
@@ -5,7 +5,7 @@ export const GROK_BROKER_TURN_END = Object.freeze({ finalGraceMs: 15_000, idleMs
|
|
|
5
5
|
export const GROK_WORKER_REAP_WAIT_MS = 10_000;
|
|
6
6
|
export function watchGrokBrokerTurnEnd(input) {
|
|
7
7
|
const timing = input.timing ?? GROK_BROKER_TURN_END, now = input.now ?? Date.now;
|
|
8
|
-
let notified, done = false;
|
|
8
|
+
let notified, done = false, toolSeenAt = 0;
|
|
9
9
|
const check = () => {
|
|
10
10
|
if (done)
|
|
11
11
|
return;
|
|
@@ -32,11 +32,22 @@ export function watchGrokBrokerTurnEnd(input) {
|
|
|
32
32
|
catch {
|
|
33
33
|
activity = undefined;
|
|
34
34
|
}
|
|
35
|
+
let tool = false;
|
|
36
|
+
try {
|
|
37
|
+
tool = input.toolProcess?.() === true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
tool = false;
|
|
41
|
+
}
|
|
42
|
+
if (tool)
|
|
43
|
+
toolSeenAt = now();
|
|
35
44
|
if (input.meter.busy() || (activity?.inFlight ?? 0) > 0)
|
|
36
45
|
return;
|
|
37
46
|
const quietSince = Math.max(input.meter.lastActivityAt(), activity?.lastActivityAt ?? 0);
|
|
47
|
+
// A tool process defers only the idle verdict. A final reply called no tool, so a child still
|
|
48
|
+
// alive beside it is not the model's work and must not hold a finished worker open.
|
|
38
49
|
const reason = final !== undefined && now() - Math.max(final.at, quietSince) >= timing.finalGraceMs ? "final_reply"
|
|
39
|
-
: now() - quietSince >= timing.idleMs ? "idle" : undefined;
|
|
50
|
+
: now() - Math.max(quietSince, toolSeenAt) >= timing.idleMs ? "idle" : undefined;
|
|
40
51
|
if (reason === undefined)
|
|
41
52
|
return;
|
|
42
53
|
stop();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a Grok worker is running one of its own local tools right now,
|
|
3
|
+
* read-only from `/proc`.
|
|
4
|
+
*
|
|
5
|
+
* The broker sees every model request (the proxy) and every MCP tool call (the
|
|
6
|
+
* facade), but not the tools Grok runs in-process: `run_terminal_command`
|
|
7
|
+
* executes as a child of the worker. Grok 1.0.34 advertises that tool with a
|
|
8
|
+
* model-chosen timeout of up to 36 000 000 ms (default 120 000 ms), so a
|
|
9
|
+
* command the model gave a long timeout is real work the broker cannot see,
|
|
10
|
+
* and the idle watchdog (`grokEngineBrokerTurnEnd.ts`) used to end such a turn
|
|
11
|
+
* as stalled ten minutes in.
|
|
12
|
+
*
|
|
13
|
+
* Every engine process carries the argv the launcher compiled — the outer
|
|
14
|
+
* bubblewrap wrapper Grok re-executes into and the Grok process inside it —
|
|
15
|
+
* so `--output-format streaming-messages-json` marks them. A live (non-zombie)
|
|
16
|
+
* process owned by `uid` whose parent is such an engine process, and which is
|
|
17
|
+
* not one itself, is a tool the worker started and is waiting on. Short-lived
|
|
18
|
+
* helpers count too, which only delays an idle verdict by a poll.
|
|
19
|
+
*
|
|
20
|
+
* An unreadable `/proc` (or one mounted `hidepid`) answers `false`: the
|
|
21
|
+
* watchdog then behaves exactly as it did without this signal.
|
|
22
|
+
*/
|
|
23
|
+
export declare function grokWorkerToolProcessLive(uid: number, procRoot?: string): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* The watchdog's poll is synchronous and `/proc` is not, so the turn keeps the
|
|
26
|
+
* last answer: `poll()` starts one read when none is pending, `live()` returns
|
|
27
|
+
* the latest completed one. A failed read answers `false`, as an unreadable
|
|
28
|
+
* `/proc` does.
|
|
29
|
+
*/
|
|
30
|
+
export declare function grokWorkerToolProcessSampler(read: () => Promise<boolean>): Readonly<{
|
|
31
|
+
poll(): void;
|
|
32
|
+
live(): boolean;
|
|
33
|
+
}>;
|
|
34
|
+
//# sourceMappingURL=grokWorkerToolProcess.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grokWorkerToolProcess.d.ts","sourceRoot":"","sources":["../../src/runtime/grokWorkerToolProcess.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAoBjG;AAQD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC;IAAE,IAAI,IAAI,IAAI,CAAC;IAAC,IAAI,IAAI,OAAO,CAAA;CAAE,CAAC,CAUtH"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a Grok worker is running one of its own local tools right now,
|
|
4
|
+
* read-only from `/proc`.
|
|
5
|
+
*
|
|
6
|
+
* The broker sees every model request (the proxy) and every MCP tool call (the
|
|
7
|
+
* facade), but not the tools Grok runs in-process: `run_terminal_command`
|
|
8
|
+
* executes as a child of the worker. Grok 1.0.34 advertises that tool with a
|
|
9
|
+
* model-chosen timeout of up to 36 000 000 ms (default 120 000 ms), so a
|
|
10
|
+
* command the model gave a long timeout is real work the broker cannot see,
|
|
11
|
+
* and the idle watchdog (`grokEngineBrokerTurnEnd.ts`) used to end such a turn
|
|
12
|
+
* as stalled ten minutes in.
|
|
13
|
+
*
|
|
14
|
+
* Every engine process carries the argv the launcher compiled — the outer
|
|
15
|
+
* bubblewrap wrapper Grok re-executes into and the Grok process inside it —
|
|
16
|
+
* so `--output-format streaming-messages-json` marks them. A live (non-zombie)
|
|
17
|
+
* process owned by `uid` whose parent is such an engine process, and which is
|
|
18
|
+
* not one itself, is a tool the worker started and is waiting on. Short-lived
|
|
19
|
+
* helpers count too, which only delays an idle verdict by a poll.
|
|
20
|
+
*
|
|
21
|
+
* An unreadable `/proc` (or one mounted `hidepid`) answers `false`: the
|
|
22
|
+
* watchdog then behaves exactly as it did without this signal.
|
|
23
|
+
*/
|
|
24
|
+
export async function grokWorkerToolProcessLive(uid, procRoot = "/proc") {
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = await readdir(procRoot);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const owned = new Map();
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (!/^\d+$/u.test(entry))
|
|
35
|
+
continue;
|
|
36
|
+
try {
|
|
37
|
+
const status = await readFile(`${procRoot}/${entry}/status`, "utf8");
|
|
38
|
+
if (Number(/^Uid:\s+(\d+)/mu.exec(status)?.[1]) !== uid)
|
|
39
|
+
continue;
|
|
40
|
+
if (/^State:\s+[ZX]/mu.test(status))
|
|
41
|
+
continue;
|
|
42
|
+
const parent = Number(/^PPid:\s+(\d+)/mu.exec(status)?.[1]);
|
|
43
|
+
if (!Number.isSafeInteger(parent))
|
|
44
|
+
continue;
|
|
45
|
+
const argv = (await readFile(`${procRoot}/${entry}/cmdline`, "utf8")).split("\0");
|
|
46
|
+
owned.set(Number(entry), { parent, engine: isGrokEngineArgv(argv) });
|
|
47
|
+
}
|
|
48
|
+
catch { /* a process exiting mid-scan is not part of the answer */ }
|
|
49
|
+
}
|
|
50
|
+
for (const process of owned.values()) {
|
|
51
|
+
if (!process.engine && owned.get(process.parent)?.engine === true)
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
/** The compiled worker argv pair every engine process of a brokered turn carries (`grokBrokerWorkerConfig.ts`). */
|
|
57
|
+
function isGrokEngineArgv(argv) {
|
|
58
|
+
const flag = argv.indexOf("--output-format");
|
|
59
|
+
return flag !== -1 && argv[flag + 1] === "streaming-messages-json";
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The watchdog's poll is synchronous and `/proc` is not, so the turn keeps the
|
|
63
|
+
* last answer: `poll()` starts one read when none is pending, `live()` returns
|
|
64
|
+
* the latest completed one. A failed read answers `false`, as an unreadable
|
|
65
|
+
* `/proc` does.
|
|
66
|
+
*/
|
|
67
|
+
export function grokWorkerToolProcessSampler(read) {
|
|
68
|
+
let live = false, pending = false;
|
|
69
|
+
return {
|
|
70
|
+
poll: () => {
|
|
71
|
+
if (pending)
|
|
72
|
+
return;
|
|
73
|
+
pending = true;
|
|
74
|
+
void Promise.resolve().then(read).then((value) => { live = value; }, () => { live = false; }).finally(() => { pending = false; });
|
|
75
|
+
},
|
|
76
|
+
live: () => live
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -226,7 +226,7 @@ function moltnetTool(agent, wakeContext) {
|
|
|
226
226
|
}
|
|
227
227
|
async function connect(agent, server) {
|
|
228
228
|
taskClockChildEnvironment(server.env);
|
|
229
|
-
const client = new Client({ name: "daimon-production", version: "0.2.
|
|
229
|
+
const client = new Client({ name: "daimon-production", version: "0.2.4" });
|
|
230
230
|
const headers = server.authSecretEnv === undefined ? {} : { authorization: `Bearer ${requiredSecret(server.authSecretEnv)}` };
|
|
231
231
|
const transport = server.transport === "stdio"
|
|
232
232
|
? new StdioClientTransport({ command: server.command, args: [...server.args], env: stringEnvironment(readyTaskClockEnvironment({ ...server.env, ...cliChildEnvironment([], agent.runtimeHomePath, { executablePath: server.command }) })) })
|