@noopolis/daimon 0.2.1 → 0.2.3
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/agentSkillsDirectory.d.ts +25 -0
- package/dist/runtime/agentSkillsDirectory.d.ts.map +1 -0
- package/dist/runtime/agentSkillsDirectory.js +101 -0
- package/dist/runtime/engineBrokerServiceConfig.d.ts +10 -2
- package/dist/runtime/engineBrokerServiceConfig.d.ts.map +1 -1
- package/dist/runtime/engineBrokerServiceConfig.js +9 -5
- package/dist/runtime/grokBrokerProjection.d.ts +17 -1
- package/dist/runtime/grokBrokerProjection.d.ts.map +1 -1
- package/dist/runtime/grokBrokerProjection.js +7 -3
- 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/grokWorkerAttestation.d.ts +3 -1
- package/dist/runtime/grokWorkerAttestation.d.ts.map +1 -1
- package/dist/runtime/grokWorkerAttestation.js +4 -2
- package/dist/runtime/grokWorkerHomeAttestation.d.ts +48 -4
- package/dist/runtime/grokWorkerHomeAttestation.d.ts.map +1 -1
- package/dist/runtime/grokWorkerHomeAttestation.js +95 -6
- package/dist/runtime/grokWorkerSkills.d.ts +86 -0
- package/dist/runtime/grokWorkerSkills.d.ts.map +1 -0
- package/dist/runtime/grokWorkerSkills.js +135 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/productionAgentTools.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type AgentSkill } from "./grokWorkerSkills.js";
|
|
2
|
+
/**
|
|
3
|
+
* What every entry of a skills tree must satisfy beyond being a plain,
|
|
4
|
+
* non-symlink directory or regular file. Attesting a root-provisioned
|
|
5
|
+
* `$GROK_HOME/skills` sets both; reading a compiled workspace leaves them unset.
|
|
6
|
+
*/
|
|
7
|
+
export type AgentSkillsTreePolicy = Readonly<{
|
|
8
|
+
ownerUid?: number;
|
|
9
|
+
readOnly?: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Reads a skills tree — `<root>/<name>/SKILL.md` plus any supporting files —
|
|
13
|
+
* into the engine-neutral {@link AgentSkill} input, or `undefined` when `root`
|
|
14
|
+
* does not exist.
|
|
15
|
+
*
|
|
16
|
+
* It is the same reader for both ends of the Grok skills path: provisioning
|
|
17
|
+
* reads the compiled workspace's `.agents/skills` (the tree Codex reads in
|
|
18
|
+
* place), and the broker re-reads the rendered `$GROK_HOME/skills` before every
|
|
19
|
+
* turn under a root-owned, read-only policy. Symlinks, special files, hard
|
|
20
|
+
* links (read-only policy), non-UTF-8 content and anything past the
|
|
21
|
+
* {@link AGENT_SKILLS_BOUNDS} are refused rather than skipped, because a
|
|
22
|
+
* skipped entry would make the attested digest describe less than Grok loads.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readAgentSkillsDirectory(root: string, policy?: AgentSkillsTreePolicy): Promise<readonly AgentSkill[] | undefined>;
|
|
25
|
+
//# sourceMappingURL=agentSkillsDirectory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentSkillsDirectory.d.ts","sourceRoot":"","sources":["../../src/runtime/agentSkillsDirectory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAyC,KAAK,UAAU,EAAuB,MAAM,uBAAuB,CAAC;AAEpH;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAKxF;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,qBAA0B,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,GAAG,SAAS,CAAC,CAqC3I"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { lstat, open, readdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { AGENT_SKILLS_BOUNDS, parseAgentSkills } from "./grokWorkerSkills.js";
|
|
5
|
+
const invalid = (detail) => new TypeError(`invalid agent skills directory: ${detail}`);
|
|
6
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
7
|
+
/**
|
|
8
|
+
* Reads a skills tree — `<root>/<name>/SKILL.md` plus any supporting files —
|
|
9
|
+
* into the engine-neutral {@link AgentSkill} input, or `undefined` when `root`
|
|
10
|
+
* does not exist.
|
|
11
|
+
*
|
|
12
|
+
* It is the same reader for both ends of the Grok skills path: provisioning
|
|
13
|
+
* reads the compiled workspace's `.agents/skills` (the tree Codex reads in
|
|
14
|
+
* place), and the broker re-reads the rendered `$GROK_HOME/skills` before every
|
|
15
|
+
* turn under a root-owned, read-only policy. Symlinks, special files, hard
|
|
16
|
+
* links (read-only policy), non-UTF-8 content and anything past the
|
|
17
|
+
* {@link AGENT_SKILLS_BOUNDS} are refused rather than skipped, because a
|
|
18
|
+
* skipped entry would make the attested digest describe less than Grok loads.
|
|
19
|
+
*/
|
|
20
|
+
export async function readAgentSkillsDirectory(root, policy = {}) {
|
|
21
|
+
let top;
|
|
22
|
+
try {
|
|
23
|
+
top = await lstat(root);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code === "ENOENT")
|
|
27
|
+
return undefined;
|
|
28
|
+
throw invalid("unreadable root");
|
|
29
|
+
}
|
|
30
|
+
assertEntry(top, "directory", policy, ".");
|
|
31
|
+
const names = await boundedEntries(root, AGENT_SKILLS_BOUNDS.skills);
|
|
32
|
+
const skills = [];
|
|
33
|
+
let budget = AGENT_SKILLS_BOUNDS.totalBytes;
|
|
34
|
+
for (const name of names) {
|
|
35
|
+
const directory = path.join(root, name);
|
|
36
|
+
assertEntry(await lstat(directory), "directory", policy, name);
|
|
37
|
+
const files = [];
|
|
38
|
+
const walk = async (relative, depth) => {
|
|
39
|
+
for (const child of await boundedEntries(path.join(directory, relative), AGENT_SKILLS_BOUNDS.filesPerSkill)) {
|
|
40
|
+
const childRelative = relative === "" ? child : `${relative}/${child}`;
|
|
41
|
+
const absolute = path.join(directory, childRelative);
|
|
42
|
+
const where = `${name}/${childRelative}`;
|
|
43
|
+
const entry = await lstat(absolute);
|
|
44
|
+
if (entry.isDirectory()) {
|
|
45
|
+
if (depth + 1 >= AGENT_SKILLS_BOUNDS.depth)
|
|
46
|
+
throw invalid(`too deep at ${JSON.stringify(where)}`);
|
|
47
|
+
assertEntry(entry, "directory", policy, where);
|
|
48
|
+
await walk(childRelative, depth + 1);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
assertEntry(entry, "file", policy, where);
|
|
52
|
+
if (files.length >= AGENT_SKILLS_BOUNDS.filesPerSkill)
|
|
53
|
+
throw invalid(`too many files in ${JSON.stringify(name)}`);
|
|
54
|
+
const content = await readPinnedFile(absolute, where, entry, Math.min(AGENT_SKILLS_BOUNDS.fileBytes, budget));
|
|
55
|
+
budget -= Buffer.byteLength(content, "utf8");
|
|
56
|
+
files.push({ path: childRelative, content });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
await walk("", 0);
|
|
60
|
+
skills.push({ name, files });
|
|
61
|
+
}
|
|
62
|
+
return parseAgentSkills(skills);
|
|
63
|
+
}
|
|
64
|
+
/** `where` is the entry's path relative to the skills root, named in every refusal. */
|
|
65
|
+
function assertEntry(entry, kind, policy, where) {
|
|
66
|
+
if (entry.isSymbolicLink() || (kind === "directory" ? !entry.isDirectory() : !entry.isFile()))
|
|
67
|
+
throw invalid(`expected a plain ${kind} at ${JSON.stringify(where)}`);
|
|
68
|
+
if (policy.ownerUid !== undefined && entry.uid !== policy.ownerUid)
|
|
69
|
+
throw invalid(`unexpected owner of ${JSON.stringify(where)}`);
|
|
70
|
+
if (policy.readOnly === true && ((Number(entry.mode) & 0o7222) !== 0 || (kind === "file" && entry.nlink !== 1)))
|
|
71
|
+
throw invalid(`writable or linked entry ${JSON.stringify(where)}`);
|
|
72
|
+
}
|
|
73
|
+
async function boundedEntries(directory, limit) {
|
|
74
|
+
const entries = await readdir(directory);
|
|
75
|
+
if (entries.length > limit)
|
|
76
|
+
throw invalid(`too many entries in ${JSON.stringify(path.basename(directory))}`);
|
|
77
|
+
return entries.sort();
|
|
78
|
+
}
|
|
79
|
+
/** Opens without following links and requires the inode `lstat` saw, so a swap between the two cannot change the bytes read. */
|
|
80
|
+
async function readPinnedFile(file, where, seen, limit) {
|
|
81
|
+
if (seen.size > limit)
|
|
82
|
+
throw invalid(`file too large ${JSON.stringify(where)}`);
|
|
83
|
+
const handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
84
|
+
try {
|
|
85
|
+
const opened = await handle.stat();
|
|
86
|
+
if (!opened.isFile() || opened.dev !== seen.dev || opened.ino !== seen.ino || opened.uid !== seen.uid || opened.mode !== seen.mode || opened.nlink !== seen.nlink || opened.size > limit)
|
|
87
|
+
throw invalid(`file changed while read ${JSON.stringify(where)}`);
|
|
88
|
+
const bytes = await handle.readFile();
|
|
89
|
+
if (bytes.length > limit)
|
|
90
|
+
throw invalid(`file too large ${JSON.stringify(where)}`);
|
|
91
|
+
try {
|
|
92
|
+
return decoder.decode(bytes);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw invalid(`file is not UTF-8 ${JSON.stringify(where)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
await handle.close().catch(() => undefined);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -16,6 +16,13 @@ export type EngineBrokerServiceRegistration = Readonly<{
|
|
|
16
16
|
usageLedgerPath: string;
|
|
17
17
|
limits: EngineBrokerTurnLimits;
|
|
18
18
|
model: GrokBrokerModelPolicy;
|
|
19
|
+
/**
|
|
20
|
+
* Digest of the skills root provisioning rendered into `$GROK_HOME/skills`
|
|
21
|
+
* (`grokWorkerSkillsSha256`), attested before every turn. Optional in v2
|
|
22
|
+
* registrations and v3 slot policies; absent means the slot has no skills and
|
|
23
|
+
* the directory must not exist.
|
|
24
|
+
*/
|
|
25
|
+
skillsSha256?: string;
|
|
19
26
|
}>;
|
|
20
27
|
/**
|
|
21
28
|
* `inferenceLedgerPath` (v2, optional) is where evaluator inference grants
|
|
@@ -46,8 +53,9 @@ export declare const engineBrokerSealLedgerPathFor: (usageLedgerPath: string) =>
|
|
|
46
53
|
* v3 retains seven-field registrations and binds slotPolicies by slot; agent IDs
|
|
47
54
|
* may repeat, while worker UIDs, slots and ledger streams must be distinct.
|
|
48
55
|
* v2 requires every registration to declare its usage ledger, limits and model
|
|
49
|
-
* (`model.id`/`model.reasoningEffort` from the closed lists)
|
|
50
|
-
*
|
|
56
|
+
* (`model.id`/`model.reasoningEffort` from the closed lists) and may declare
|
|
57
|
+
* `skillsSha256` (v3: on its slot policy); unknown keys at any level are
|
|
58
|
+
* refused. v1 is still accepted and receives today's defaults:
|
|
51
59
|
* the container ledger, {@link DEFAULT_GROK_BROKER_TURN_LIMITS}, and
|
|
52
60
|
* `grok-4.6`/`low`.
|
|
53
61
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engineBrokerServiceConfig.d.ts","sourceRoot":"","sources":["../../src/runtime/engineBrokerServiceConfig.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgE,KAAK,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC5I,OAAO,EAAuF,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAK7J,eAAO,MAAM,wBAAwB,EAAG,0CAAmD,CAAC;AAC5F,eAAO,MAAM,wBAAwB,EAAG,0CAAmD,CAAC;AAC5F,eAAO,MAAM,wBAAwB,EAAG,0CAAmD,CAAC;AAE5F,mHAAmH;AACnH,MAAM,MAAM,+BAA+B,GAAG,QAAQ,CAAC;IACrD,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IACpI,oJAAoJ;IACpJ,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,sBAAsB,CAAC;IAC/B,KAAK,EAAE,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"engineBrokerServiceConfig.d.ts","sourceRoot":"","sources":["../../src/runtime/engineBrokerServiceConfig.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgE,KAAK,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC5I,OAAO,EAAuF,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAK7J,eAAO,MAAM,wBAAwB,EAAG,0CAAmD,CAAC;AAC5F,eAAO,MAAM,wBAAwB,EAAG,0CAAmD,CAAC;AAC5F,eAAO,MAAM,wBAAwB,EAAG,0CAAmD,CAAC;AAE5F,mHAAmH;AACnH,MAAM,MAAM,+BAA+B,GAAG,QAAQ,CAAC;IACrD,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IACpI,oJAAoJ;IACpJ,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,sBAAsB,CAAC;IAC/B,KAAK,EAAE,qBAAqB,CAAC;IAC7B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC,CAAC;AACH;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,SAAS,+BAA+B,EAAE,CAAC;IAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAc5O,2EAA2E;AAC3E,eAAO,MAAM,gCAAgC,GAAI,iBAAiB,MAAM,KAAG,MAAgF,CAAC;AAE5J;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,GAAI,iBAAiB,MAAM,KAAG,MAA6E,CAAC;AAEtJ;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,yBAAyB,CAyDxF"}
|
|
@@ -33,8 +33,9 @@ export const engineBrokerSealLedgerPathFor = (usageLedgerPath) => path.posix.joi
|
|
|
33
33
|
* v3 retains seven-field registrations and binds slotPolicies by slot; agent IDs
|
|
34
34
|
* may repeat, while worker UIDs, slots and ledger streams must be distinct.
|
|
35
35
|
* v2 requires every registration to declare its usage ledger, limits and model
|
|
36
|
-
* (`model.id`/`model.reasoningEffort` from the closed lists)
|
|
37
|
-
*
|
|
36
|
+
* (`model.id`/`model.reasoningEffort` from the closed lists) and may declare
|
|
37
|
+
* `skillsSha256` (v3: on its slot policy); unknown keys at any level are
|
|
38
|
+
* refused. v1 is still accepted and receives today's defaults:
|
|
38
39
|
* the container ledger, {@link DEFAULT_GROK_BROKER_TURN_LIMITS}, and
|
|
39
40
|
* `grok-4.6`/`low`.
|
|
40
41
|
*/
|
|
@@ -66,7 +67,7 @@ export function parseEngineBrokerServiceConfig(value) {
|
|
|
66
67
|
for (const policy of value.slotPolicies) {
|
|
67
68
|
if (!plain(policy))
|
|
68
69
|
throw invalid();
|
|
69
|
-
exact(policy, ["slot", "usageLedgerPath", "limits", "model"]);
|
|
70
|
+
exact(policy, ["slot", "usageLedgerPath", "limits", "model", ...withSkills(policy)]);
|
|
70
71
|
if (!Number.isSafeInteger(policy.slot) || policy.slot < 0 || policies.has(policy.slot))
|
|
71
72
|
throw invalid();
|
|
72
73
|
policies.set(policy.slot, policy);
|
|
@@ -77,7 +78,7 @@ export function parseEngineBrokerServiceConfig(value) {
|
|
|
77
78
|
const registrations = value.registrations.map((entry) => {
|
|
78
79
|
if (!plain(entry))
|
|
79
80
|
throw invalid();
|
|
80
|
-
exact(entry, v2 && !v3 ? V2_REGISTRATION : V1_REGISTRATION);
|
|
81
|
+
exact(entry, v2 && !v3 ? [...V2_REGISTRATION, ...withSkills(entry)] : V1_REGISTRATION);
|
|
81
82
|
const { agentId, slot, workerUid, workspace, profilePath, eventsPath, profileSha256 } = entry;
|
|
82
83
|
if (typeof agentId !== "string" || !agentId.trim() || (!v3 && seen.has(agentId)) || !Number.isSafeInteger(slot) || slot < 0 || slots.has(slot) || !Number.isSafeInteger(workerUid) || workerUid < 2200 || workers.has(workerUid) || !absolute(workspace) || !absolute(profilePath) || !absolute(eventsPath) || typeof profileSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(profileSha256) || eventsPath !== grokWorkerEventsPathFor(profilePath) || !profilePath.endsWith("/sandbox.toml"))
|
|
83
84
|
throw invalid();
|
|
@@ -104,7 +105,9 @@ export function parseEngineBrokerServiceConfig(value) {
|
|
|
104
105
|
catch {
|
|
105
106
|
throw invalid();
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
+
if (Object.hasOwn(policy, "skillsSha256") && (typeof policy.skillsSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(policy.skillsSha256)))
|
|
109
|
+
throw invalid();
|
|
110
|
+
return { ...base, usageLedgerPath, limits, model: parseServiceModel(policy.model), ...(Object.hasOwn(policy, "skillsSha256") ? { skillsSha256: policy.skillsSha256 } : {}) };
|
|
108
111
|
});
|
|
109
112
|
const base = { credentialHome: value.credentialHome, turnStore: value.turnStore, registrations,
|
|
110
113
|
...(value.runRequestStore === undefined ? {} : { runRequestStore: value.runRequestStore }),
|
|
@@ -119,6 +122,7 @@ export function parseEngineBrokerServiceConfig(value) {
|
|
|
119
122
|
throw invalid();
|
|
120
123
|
return { ...base, inferenceLedgerPath };
|
|
121
124
|
}
|
|
125
|
+
const withSkills = (value) => Object.hasOwn(value, "skillsSha256") ? ["skillsSha256"] : [];
|
|
122
126
|
const ledgerPath = (item) => absolute(item) && item.endsWith(".jsonl") && path.posix.normalize(item) === item;
|
|
123
127
|
function parseServiceModel(value) {
|
|
124
128
|
if (!plain(value))
|
|
@@ -2,6 +2,7 @@ import { GROK_BROKER_TASK_CLOCK, type GrokWorkerConfigVariant } from "../contrac
|
|
|
2
2
|
import { type EngineBrokerServiceRegistration } from "./engineBrokerServiceConfig.js";
|
|
3
3
|
import { type EngineBrokerTurnLimits } from "./engineBrokerTurnAccounting.js";
|
|
4
4
|
import type { GrokBrokerModel, GrokBrokerReasoningEffort } from "./grokBrokerModelPolicy.js";
|
|
5
|
+
import { type AgentSkill } from "./grokWorkerSkills.js";
|
|
5
6
|
import { GROK_WORKER_SANDBOX_PROFILE } from "./grokWorkerSandboxProfile.js";
|
|
6
7
|
export declare const GROK_BROKER_PROJECTION_VERSION: "noopolis.daimon.grok-broker-projection.v3";
|
|
7
8
|
/**
|
|
@@ -27,6 +28,13 @@ export type OrganizationGrokBrokerProjection = Readonly<{
|
|
|
27
28
|
timestampFilePath: string;
|
|
28
29
|
}>;
|
|
29
30
|
systemPromptSha256: string;
|
|
31
|
+
/**
|
|
32
|
+
* Digest of the declared skills rendered into `$GROK_HOME/skills`
|
|
33
|
+
* (`grokWorkerSkillsSha256`). Present only when the agent declares at least
|
|
34
|
+
* one skill, so a skill-less projection is byte-identical to one computed
|
|
35
|
+
* before skills existed.
|
|
36
|
+
*/
|
|
37
|
+
skillsSha256?: string;
|
|
30
38
|
grokCliVersion: string;
|
|
31
39
|
grokExecutableSha256: string;
|
|
32
40
|
nativeAbiVersion: number;
|
|
@@ -78,6 +86,13 @@ export type OrganizationGrokBrokerProjectionOptions = Readonly<{
|
|
|
78
86
|
seccompProfileSha256: string;
|
|
79
87
|
/** When the caller already holds a rendered profile digest, it must equal Daimon's. */
|
|
80
88
|
profileSha256?: string;
|
|
89
|
+
/**
|
|
90
|
+
* The agent's declared skills: the compiled workspace `.agents/skills` tree
|
|
91
|
+
* every engine is given (`readAgentSkillsDirectory`), which root provisioning
|
|
92
|
+
* renders into `$GROK_HOME/skills` (`renderGrokWorkerSkillsTree`). Omitted or
|
|
93
|
+
* empty means no skills.
|
|
94
|
+
*/
|
|
95
|
+
skills?: readonly AgentSkill[];
|
|
81
96
|
}>;
|
|
82
97
|
/**
|
|
83
98
|
* Resolve the public Grok broker projection for one agent.
|
|
@@ -98,7 +113,8 @@ export type OrganizationGrokBrokerProjectionOptions = Readonly<{
|
|
|
98
113
|
* this agent (realm, bootstrap, peers, and the mask covering the acceptance
|
|
99
114
|
* store) plus the caller's evaluator paths, sorted and deduplicated exactly as
|
|
100
115
|
* the profile renderer does. A supplied `profileSha256` that differs is
|
|
101
|
-
* refused.
|
|
116
|
+
* refused. Declared `skills` add `skillsSha256`, which the implied
|
|
117
|
+
* registration carries and the broker attests against `$GROK_HOME/skills`.
|
|
102
118
|
*/
|
|
103
119
|
export declare function resolveOrganizationGrokBrokerProjection(config: unknown, agentId: string, options: OrganizationGrokBrokerProjectionOptions): OrganizationGrokBrokerProjection;
|
|
104
120
|
/** sha256 over the projection's canonical JSON; what a slot preflight receipt binds. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grokBrokerProjection.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProjection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,KAAK,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AAQ3G,OAAO,EAAkC,KAAK,+BAA+B,EAAE,MAAM,gCAAgC,CAAC;AACtH,OAAO,EAA+B,KAAK,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAE3G,OAAO,KAAK,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAC7F,OAAO,EAAE,2BAA2B,EAA2F,MAAM,+BAA+B,CAAC;AAGrK,eAAO,MAAM,8BAA8B,6CAAuC,CAAC;AAEnF;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD,OAAO,EAAE,OAAO,8BAA8B,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C,SAAS,CAAC,EAAE,IAAI,CAAC,OAAO,sBAAsB,EAAE,eAAe,GAAG,mBAAmB,CAAC,GAAG,QAAQ,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxJ,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,eAAe,CAAC;IACvB,eAAe,EAAE,yBAAyB,CAAC;IAC3C,MAAM,EAAE,sBAAsB,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,yHAAyH;IACzH,oBAAoB,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,QAAQ,CAAC;QAAE,QAAQ,EAAE,gBAAgB,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAC;QAAC,eAAe,EAAE,IAAI,CAAC;QAAC,WAAW,EAAE,OAAO,2BAA2B,CAAC;QAAC,cAAc,EAAE,YAAY,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjM,CAAC,CAAC;AAEH,MAAM,MAAM,uCAAuC,GAAG,QAAQ,CAAC;IAC7D,gHAAgH;IAChH,mBAAmB,CAAC,EAAE,uBAAuB,CAAC;IAC9C,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,OAAO,GAAG,KAAK,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,sBAAsB,CAAC;IAC/B;;;;;;;;;;;;;;OAcG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mFAAmF;IACnF,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,gFAAgF;IAChF,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"grokBrokerProjection.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProjection.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@ import { grokSandboxProtectedPaths } from "./engineDispatcher.js";
|
|
|
9
9
|
import { parseEngineBrokerServiceConfig } from "./engineBrokerServiceConfig.js";
|
|
10
10
|
import { parseEngineBrokerTurnLimits } from "./engineBrokerTurnAccounting.js";
|
|
11
11
|
import { grokBrokerWorkerConfigPins, grokBrokerWorkerConfigSha256 } from "./grokBrokerWorkerConfig.js";
|
|
12
|
+
import { grokWorkerSkillsSha256, parseAgentSkills } from "./grokWorkerSkills.js";
|
|
12
13
|
import { GROK_WORKER_SANDBOX_PROFILE, grokWorkerEventsPathFor, renderGrokWorkerSandboxProfile, grokWorkerSandboxProfileSha256 } from "./grokWorkerSandboxProfile.js";
|
|
13
14
|
import { parseOrganizationRuntimeConfig } from "./organizationRuntime.js";
|
|
14
15
|
export const GROK_BROKER_PROJECTION_VERSION = GROK_ENGINE_BROKER.projectionVersion;
|
|
@@ -31,7 +32,8 @@ export const GROK_BROKER_PROJECTION_VERSION = GROK_ENGINE_BROKER.projectionVersi
|
|
|
31
32
|
* this agent (realm, bootstrap, peers, and the mask covering the acceptance
|
|
32
33
|
* store) plus the caller's evaluator paths, sorted and deduplicated exactly as
|
|
33
34
|
* the profile renderer does. A supplied `profileSha256` that differs is
|
|
34
|
-
* refused.
|
|
35
|
+
* refused. Declared `skills` add `skillsSha256`, which the implied
|
|
36
|
+
* registration carries and the broker attests against `$GROK_HOME/skills`.
|
|
35
37
|
*/
|
|
36
38
|
export function resolveOrganizationGrokBrokerProjection(config, agentId, options) {
|
|
37
39
|
const parsed = parseOrganizationRuntimeConfig(config);
|
|
@@ -64,12 +66,13 @@ export function resolveOrganizationGrokBrokerProjection(config, agentId, options
|
|
|
64
66
|
const artifact = GROK_ENGINE_BROKER.grokCliArtifacts[options.architecture];
|
|
65
67
|
if (artifact === undefined)
|
|
66
68
|
throw new Error("Grok broker projection requires a pinned architecture");
|
|
69
|
+
const skills = parseAgentSkills(options.skills ?? []);
|
|
67
70
|
const profilePath = path.posix.join(options.workerHomePath, ".grok", "sandbox.toml");
|
|
68
71
|
const projection = {
|
|
69
72
|
version: GROK_BROKER_PROJECTION_VERSION, agentId, workspacePath: agent.workspacePath, runtimeHomePath: agent.runtimeHomePath,
|
|
70
73
|
workerUid: options.workerUid, slot: options.slot, profilePath, profileSha256, denyPaths, workerConfigSha256, workerConfigVariant,
|
|
71
74
|
...(workerConfigVariant === "clocked" ? { taskClock: { ...GROK_BROKER_TASK_CLOCK, directoryPath: path.posix.dirname(brokerTaskClockFileFor(options.slot)), timestampFilePath: brokerTaskClockFileFor(options.slot) } } : {}),
|
|
72
|
-
systemPromptSha256: GROK_ENGINE_BROKER.worker.systemPromptSha256, grokCliVersion: GROK_ENGINE_BROKER.grokCliVersion, grokExecutableSha256: artifact.sha256,
|
|
75
|
+
systemPromptSha256: GROK_ENGINE_BROKER.worker.systemPromptSha256, ...(skills.length === 0 ? {} : { skillsSha256: grokWorkerSkillsSha256(skills) }), grokCliVersion: GROK_ENGINE_BROKER.grokCliVersion, grokExecutableSha256: artifact.sha256,
|
|
73
76
|
nativeAbiVersion: GROK_ENGINE_BROKER.nativeAbiVersion, model, reasoningEffort, limits: parseEngineBrokerTurnLimits(options.limits), usageLedgerPath: options.usageLedgerPath, seccompProfileSha256: options.seccompProfileSha256,
|
|
74
77
|
attestation: { platform: "linux/landlock", enforced: true, restrictNetwork: true, profileName: GROK_WORKER_SANDBOX_PROFILE, sandboxRuntime: "bubblewrap", eventsPath: grokWorkerEventsPathFor(profilePath) }
|
|
75
78
|
};
|
|
@@ -84,7 +87,8 @@ export function grokBrokerServiceRegistrationFor(projection) {
|
|
|
84
87
|
const registration = {
|
|
85
88
|
agentId: projection.agentId, slot: projection.slot, workerUid: projection.workerUid, workspace: projection.workspacePath,
|
|
86
89
|
profilePath: projection.profilePath, eventsPath: projection.attestation.eventsPath, profileSha256: projection.profileSha256,
|
|
87
|
-
usageLedgerPath: projection.usageLedgerPath, limits: projection.limits, model: { id: projection.model, reasoningEffort: projection.reasoningEffort }
|
|
90
|
+
usageLedgerPath: projection.usageLedgerPath, limits: projection.limits, model: { id: projection.model, reasoningEffort: projection.reasoningEffort },
|
|
91
|
+
...(projection.skillsSha256 === undefined ? {} : { skillsSha256: projection.skillsSha256 })
|
|
88
92
|
};
|
|
89
93
|
parseEngineBrokerServiceConfig({ version: "noopolis.daimon.engine-broker-service.v2", credentialHome: GROK_ENGINE_BROKER.credentialHomePath, turnStore: GROK_ENGINE_BROKER.turnStorePath, registrations: [registration] });
|
|
90
94
|
return registration;
|
|
@@ -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"}
|