agent-control-plane-core 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -3
- package/package.json +1 -1
- package/schema/control-plane/v1/agents.json +86 -1
- package/src/adapters/amp.mjs +21 -0
- package/src/adapters/claude.mjs +38 -0
- package/src/adapters/codex.mjs +164 -53
- package/src/adapters/gemini.mjs +36 -0
- package/src/agent-manifest.mjs +9 -1
- package/src/conformance.mjs +204 -4
- package/src/control-plane.mjs +183 -5
- package/src/fixtures/codex.json +11 -5
- package/types/adapters/amp.d.mts +12 -0
- package/types/adapters/claude.d.mts +15 -0
- package/types/adapters/codex.d.mts +55 -12
- package/types/adapters/gemini.d.mts +19 -0
- package/types/conformance.d.mts +7 -1
- package/types/control-plane.d.mts +104 -6
|
@@ -36,15 +36,17 @@ export function parse(native: any, { version }?: {
|
|
|
36
36
|
* on an enforceable deny. On a pre-v0.135 Codex (`this_call_vetoable` false) the
|
|
37
37
|
* body still renders but exit stays 0 and `enforced` is false — advisory only.
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
* decision rides `hookSpecificOutput.permissionDecision`, a
|
|
41
|
-
*
|
|
42
|
-
*
|
|
39
|
+
* Each native event gets its OWN shape, because Codex parses each strictly: a
|
|
40
|
+
* PreToolUse decision rides `hookSpecificOutput.permissionDecision`, a
|
|
41
|
+
* PermissionRequest one `hookSpecificOutput.decision.behavior`, and a
|
|
42
|
+
* post-tool one the top-level `decision`/`reason` pair.
|
|
43
43
|
*
|
|
44
44
|
* `soleGate` (default `false`) is the same dangerous, explicit opt-in as the
|
|
45
|
-
* Claude adapter
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* Claude adapter, and acts only on PermissionRequest: there an `allow` renders
|
|
46
|
+
* `behavior: "allow"`, which grants the approval (except, from 0.166, when the
|
|
47
|
+
* hook is environment-sourced). PreToolUse has no allow that
|
|
48
|
+
* approves anything (Codex accepts `"allow"` only beside `updatedInput`, as a
|
|
49
|
+
* rewrite), and post-tool there is no approval left to grant.
|
|
48
50
|
* @param {Verdict} verdict
|
|
49
51
|
* @param {ToolCallEvent} event
|
|
50
52
|
* @param {{ soleGate?: boolean }} [options]
|
|
@@ -103,11 +105,15 @@ export const INTEGRATION_MODE: "external_hook";
|
|
|
103
105
|
/** @type {import("../control-plane.mjs").CoverageMap} */
|
|
104
106
|
export const COVERAGE: import("../control-plane.mjs").CoverageMap;
|
|
105
107
|
/**
|
|
106
|
-
* Which {@link VERDICT_CONTENT_FIELDS} have no native channel on each kind
|
|
107
|
-
* `render` drops them.
|
|
108
|
-
* `updatedInput`
|
|
109
|
-
* `
|
|
110
|
-
*
|
|
108
|
+
* Which {@link VERDICT_CONTENT_FIELDS} have no native channel on each kind's
|
|
109
|
+
* {@link NATIVE_EVENT_FOR} event, so `render` drops them. PreToolUse takes
|
|
110
|
+
* `updatedInput` (applied only beside `permissionDecision: "allow"`) and
|
|
111
|
+
* `additionalContext`; PostToolUse takes `additionalContext` alone ([X8], read
|
|
112
|
+
* at `rust-v0.166.0` and identical at `rust-v0.135.0`). `PermissionRequest`,
|
|
113
|
+
* which also parses as `pre_tool`, takes neither: its schema is
|
|
114
|
+
* `{hookEventName, decision: {behavior, message}}`, and a body carrying
|
|
115
|
+
* `updatedInput` is ignored (Codex's own approval flow decides), so
|
|
116
|
+
* {@link render} drops both there.
|
|
111
117
|
*
|
|
112
118
|
* NEITHER kind can rewrite tool output: PostToolUse "can't undo side effects
|
|
113
119
|
* from a tool that already ran", and Codex rejects `updatedMCPToolOutput`, so
|
|
@@ -119,6 +125,43 @@ export const COVERAGE: import("../control-plane.mjs").CoverageMap;
|
|
|
119
125
|
* @type {Record<string, ReadonlySet<string>|undefined>}
|
|
120
126
|
*/
|
|
121
127
|
export const UNRENDERED_FIELDS: Record<string, ReadonlySet<string> | undefined>;
|
|
128
|
+
/**
|
|
129
|
+
* How Codex's `PreToolUse` hook files a model tool call ([X7], read from
|
|
130
|
+
* Codex's source at `rust-v0.166.0`). A function tool is filed under its flat
|
|
131
|
+
* name (namespace + name) with its parsed arguments by default; these
|
|
132
|
+
* handlers supply their own payload or none:
|
|
133
|
+
* - `exec_command` → `Bash` with `{command: <cmd>}`; arguments Codex cannot
|
|
134
|
+
* parse as `ExecCommandArgs` file nothing, which this table models only for
|
|
135
|
+
* a missing or null `cmd` or scalar arguments. Not modelled: other type
|
|
136
|
+
* errors, and a positional array, which serde reads (`["ls"]` is `cmd: "ls"`).
|
|
137
|
+
* - `apply_patch` (a freeform tool) → `apply_patch` with its raw patch text as
|
|
138
|
+
* `command`.
|
|
139
|
+
* - `spawn_agent` in the `multi_agent_v1` namespace → `spawn_agent`.
|
|
140
|
+
* - `write_stdin`, code mode's `wait`, and code mode's freeform `exec` → no hook.
|
|
141
|
+
* These rules assume the named tool owns the name; a client-defined tool
|
|
142
|
+
* called `wait` with code mode off takes the default payload.
|
|
143
|
+
* The default payload is function-only, so a freeform extension tool files
|
|
144
|
+
* nothing too — the mini-language's rule for an unlisted freeform call.
|
|
145
|
+
* NOT MODELLED: Codex files an MCP call as `mcp__<namespace>__<tool>`, with
|
|
146
|
+
* `_` trimmed from the namespace's end and the tool's start (`mcp.rs`
|
|
147
|
+
* `hook_tool_name`), which can differ from the flat name — a rule keyed by
|
|
148
|
+
* one name cannot express that.
|
|
149
|
+
* @type {import("../control-plane.mjs").PreToolFiling}
|
|
150
|
+
*/
|
|
151
|
+
export const PRE_TOOL_FILING: import("../control-plane.mjs").PreToolFiling;
|
|
152
|
+
/**
|
|
153
|
+
* Codex's `PreToolUse` output parser ([X8], `rust-v0.166.0`) deserializes the
|
|
154
|
+
* body into `deny_unknown_fields` structs at both levels: these seven top-level
|
|
155
|
+
* keys (`PreToolUseCommandOutputWire` with its flattened universal fields), and
|
|
156
|
+
* these five in `hookSpecificOutput`. Any other key fails the whole body: the
|
|
157
|
+
* run is marked failed, no block is set, and the call RUNS. A known key can
|
|
158
|
+
* still void the body by its value or combination (`continue: false`, any
|
|
159
|
+
* `stopReason`, `suppressOutput: true`, `decision: "approve"`, `updatedInput`
|
|
160
|
+
* without `permissionDecision: "allow"`), which this declaration does not
|
|
161
|
+
* describe.
|
|
162
|
+
* @type {import("../control-plane.mjs").PreToolResponse}
|
|
163
|
+
*/
|
|
164
|
+
export const PRE_TOOL_RESPONSE: import("../control-plane.mjs").PreToolResponse;
|
|
122
165
|
/** Minimum Codex version whose hook can actually veto a tool call. */
|
|
123
166
|
export const MIN_ENFORCING_VERSION: readonly number[];
|
|
124
167
|
/**
|
|
@@ -126,6 +126,25 @@ export const GEMINI_TOOL_ALIASES: Readonly<Record<string, string>>;
|
|
|
126
126
|
* here, because an un-escalated ask lets Gemini run the tool.
|
|
127
127
|
*/
|
|
128
128
|
export const NATIVE_ASK_TIER: false;
|
|
129
|
+
/**
|
|
130
|
+
* No rename is modelled. Gemini CLI hands `BeforeTool` the invocation's tool
|
|
131
|
+
* name beside an `originalRequestName`, so an aliased call may be filed under
|
|
132
|
+
* a name the model did not use; that is unverified, so not published.
|
|
133
|
+
* @type {import("../control-plane.mjs").PreToolFiling}
|
|
134
|
+
*/
|
|
135
|
+
export const PRE_TOOL_FILING: import("../control-plane.mjs").PreToolFiling;
|
|
136
|
+
/**
|
|
137
|
+
* Gemini CLI keeps `hookSpecificOutput` as a `Record<string, unknown>` and its
|
|
138
|
+
* `BeforeTool` path reads only `tool_input` from it, by `in` lookup, so any
|
|
139
|
+
* other key is ignored (`packages/core/src/hooks/types.ts`
|
|
140
|
+
* `BeforeToolHookOutput`; both firing paths, `core/coreToolHookTriggers.ts`
|
|
141
|
+
* and `scheduler/hook-utils.ts` `evaluateBeforeToolHook`, read at gemini-cli
|
|
142
|
+
* `62bab5b2`). The top level is the `HookOutput` interface's seven keys, read
|
|
143
|
+
* from an unchecked `JSON.parse` cast (`hooks/hookRunner.ts`), so an unknown
|
|
144
|
+
* top-level key is ignored too.
|
|
145
|
+
* @type {import("../control-plane.mjs").PreToolResponse}
|
|
146
|
+
*/
|
|
147
|
+
export const PRE_TOOL_RESPONSE: import("../control-plane.mjs").PreToolResponse;
|
|
129
148
|
/** @type {import("../control-plane.mjs").Adapter} */
|
|
130
149
|
export const geminiAdapter: import("../control-plane.mjs").Adapter;
|
|
131
150
|
export type ToolCallEvent = import("../control-plane.mjs").ToolCallEvent;
|
package/types/conformance.d.mts
CHANGED
|
@@ -132,13 +132,18 @@ export function assertAliasedInputsCanonical(fixturesList: any[], assert: any):
|
|
|
132
132
|
* decide where an `ask` must be escalated to a `deny`, so a wrong one
|
|
133
133
|
* either lets a call through or denies every asked call on the hosts that
|
|
134
134
|
* do ask. OBSERVE_ONLY renders are exempt, as in rule ⑧.
|
|
135
|
+
* 12. pre-tool key honesty: the adapter declares a well-formed
|
|
136
|
+
* `PRE_TOOL_FILING` and `PRE_TOOL_RESPONSE`, and every render on the host's
|
|
137
|
+
* native pre-tool event — fixture, rule ⑧ and rule ⑩ probes alike — puts
|
|
138
|
+
* in `hookSpecificOutput` only keys `PRE_TOOL_RESPONSE` says the host
|
|
139
|
+
* reads. At least one such render must be checked.
|
|
135
140
|
*
|
|
136
141
|
* `assert` is injected (node:assert/strict) so the harness stays test-framework
|
|
137
142
|
* neutral; it throws on the first mismatch. Returns a summary the caller can
|
|
138
143
|
* assert further on.
|
|
139
144
|
*
|
|
140
145
|
* @param {{ adapter: import("./control-plane.mjs").Adapter, fixtures: any, assert: any }} args
|
|
141
|
-
* @returns {{ cases: number, renders: number, decisionsSeen: Set<string>, mutationSeen: boolean, contentFieldsSeen: Set<string>, enforcedDenySeen: boolean, vetoableDenySeen: boolean, unknownKindSeen: boolean, coverageClassesChecked: Set<string>, unenforceableDenyChecks: number, askTierChecks: number }}
|
|
146
|
+
* @returns {{ cases: number, renders: number, decisionsSeen: Set<string>, mutationSeen: boolean, contentFieldsSeen: Set<string>, enforcedDenySeen: boolean, vetoableDenySeen: boolean, unknownKindSeen: boolean, coverageClassesChecked: Set<string>, unenforceableDenyChecks: number, askTierChecks: number, preToolKeyChecks: number }}
|
|
142
147
|
*/
|
|
143
148
|
export function runAdapterConformance({ adapter, fixtures, assert }: {
|
|
144
149
|
adapter: import("./control-plane.mjs").Adapter;
|
|
@@ -156,4 +161,5 @@ export function runAdapterConformance({ adapter, fixtures, assert }: {
|
|
|
156
161
|
coverageClassesChecked: Set<string>;
|
|
157
162
|
unenforceableDenyChecks: number;
|
|
158
163
|
askTierChecks: number;
|
|
164
|
+
preToolKeyChecks: number;
|
|
159
165
|
};
|
|
@@ -124,6 +124,40 @@ export function classifyCallClass(tool: string | null, native?: Record<string, u
|
|
|
124
124
|
* @returns {ReadonlySet<string>}
|
|
125
125
|
*/
|
|
126
126
|
export function readonlySet(values: Iterable<string>): ReadonlySet<string>;
|
|
127
|
+
/**
|
|
128
|
+
* The model tool name a {@link PreToolFiling} is keyed by: `name`, prefixed
|
|
129
|
+
* (no separator) with `namespace` unless that is absent, empty, or Codex's
|
|
130
|
+
* default `functions` namespace (`flat_tool_name`, `is_default_namespace`;
|
|
131
|
+
* [X7] in `docs/hook-coverage-matrix.md`).
|
|
132
|
+
* @param {string | null | undefined} namespace
|
|
133
|
+
* @param {string} name
|
|
134
|
+
* @returns {string}
|
|
135
|
+
*/
|
|
136
|
+
export function flatToolName(namespace: string | null | undefined, name: string): string;
|
|
137
|
+
/**
|
|
138
|
+
* Whether `rule` is a non-null {@link PreToolFiling} rule: a plain object with
|
|
139
|
+
* exactly a non-empty string `tool_name` and a well-formed `input`. The one
|
|
140
|
+
* grammar both {@link fileModelCall} and the conformance harness hold a rule to.
|
|
141
|
+
* @param {unknown} rule
|
|
142
|
+
* @returns {boolean}
|
|
143
|
+
*/
|
|
144
|
+
export function isFilingRule(rule: unknown): boolean;
|
|
145
|
+
/**
|
|
146
|
+
* The call a host's pre-tool hook files for a model call of `kind` to `name`
|
|
147
|
+
* with arguments value `args`, per that host's {@link PreToolFiling}; `null`
|
|
148
|
+
* when the hook files none. The reference reading of the mini-language
|
|
149
|
+
* `agents.json` publishes as `pre_tool_filing`. Throws on a rule outside that
|
|
150
|
+
* grammar or a kind outside {@link MODEL_CALL_KINDS}.
|
|
151
|
+
* @param {PreToolFiling} filing
|
|
152
|
+
* @param {"function" | "freeform"} kind whether the model made a function call or a freeform (custom) call
|
|
153
|
+
* @param {string} name the model tool name, a {@link flatToolName}
|
|
154
|
+
* @param {unknown} args the call's arguments value
|
|
155
|
+
* @returns {{ tool_name: string, tool_input: unknown } | null}
|
|
156
|
+
*/
|
|
157
|
+
export function fileModelCall(filing: PreToolFiling, kind: "function" | "freeform", name: string, args: unknown): {
|
|
158
|
+
tool_name: string;
|
|
159
|
+
tool_input: unknown;
|
|
160
|
+
} | null;
|
|
127
161
|
/**
|
|
128
162
|
* The translator for one agent's protocol. `parse` maps a native event to a
|
|
129
163
|
* {@link ToolCallEvent} (never throwing on unmodelled input, stamping the
|
|
@@ -137,7 +171,9 @@ export function readonlySet(values: Iterable<string>): ReadonlySet<string>;
|
|
|
137
171
|
* @property {(verdict: Verdict, event: ToolCallEvent, options?: { soleGate?: boolean }) => NativeResponse} render
|
|
138
172
|
* @property {Record<string, string|undefined>} [NATIVE_EVENT_FOR] the native event name this host uses for each {@link EventKind}, for a conformance probe of a kind no fixture produced. Optional: a kind absent here (`unknown` always, plus any kind this transport does not carry) is probed with a marker name, which takes the adapter's unrecognized-event branch. Never read at runtime — `parse` stamps the real name on `meta.native_event`.
|
|
139
173
|
* @property {boolean} NATIVE_ASK_TIER whether this host's transport carries a DISTINCT "ask the human" tier that the host honours. `true` ⇒ a rendered `ask` suspends the call in front of a person (Claude Code's `permissionDecision: "ask"`, Amp's exit 1). `false` ⇒ the host has no such tier, so `render` collapses `ask` onto another signal (Gemini CLI writes its advisory `decision: "deny"`) and an un-escalated ask lets the tool run. A consumer that must not let an ask through reads this to decide where to escalate `ask` to `deny`, instead of keeping its own hand-typed list of agent ids. It is about the HOST, not the verdict: `ask` never renders as `enforced` on any adapter, and the tier it names is the PRE-TOOL gate — once a tool has run there is nothing left to suspend, so every host renders a post-tool `ask` as its block. The conformance harness probes the real `render` against it — a `true` whose ask renders as the host's abstaining allow fails, and so does a `false` whose ask differs from its own advisory deny.
|
|
140
|
-
* @property {
|
|
174
|
+
* @property {PreToolFiling} PRE_TOOL_FILING how this host's pre-tool hook files a model tool call, for a consumer matching the model's calls against the hook's. `{}` when no rename is modelled.
|
|
175
|
+
* @property {PreToolResponse} PRE_TOOL_RESPONSE the top-level and `hookSpecificOutput` keys this host reads on its pre-tool event, each list with whether an unread key there runs the call. The conformance harness holds every pre-tool render to both lists.
|
|
176
|
+
* @property {Record<string, ReadonlySet<string>|undefined>} UNRENDERED_FIELDS per-event-kind set of {@link VERDICT_CONTENT_FIELDS} this host has no native channel for, so `render` drops them. EVERY {@link EventKind} carries a row, including one this adapter's `parse` cannot emit — an omission would otherwise read as "every content field reaches a channel here", the reverse of the truth on a transport that carries none. The value type still admits `undefined` so a consumer handles a lookup miss rather than indexing straight into `.has(...)`; the conformance harness refuses a missing row. SCOPE — a row describes the ALLOW path on the kind's `NATIVE_EVENT_FOR` event only; another native event of that kind (Codex's `PermissionRequest`) may drop more. An enforceable deny may drop more: Gemini's exit-2 System Block returns no stdout at all, so a deny there carries no `additional_context` whatever the row says. Do not read this map to infer what a deny delivers. The conformance harness fails an adapter whose renders disagree with its declaration either way, so a stale entry cannot survive.
|
|
141
177
|
*/
|
|
142
178
|
/**
|
|
143
179
|
* Build a normalized {@link ToolCallEvent}, stamping the schema version. Pure —
|
|
@@ -354,10 +390,10 @@ export function renderEscalated(adapter: Adapter, verdict: Verdict, event: ToolC
|
|
|
354
390
|
* {@link Adapter} interface an integrator implements. A new REQUIRED adapter
|
|
355
391
|
* member breaks every third-party adapter while every event on the wire stays
|
|
356
392
|
* byte-identical, so it is a package-semver break and leaves
|
|
357
|
-
* {@link CONTROL_PLANE_SCHEMA} alone. `UNRENDERED_FIELDS`
|
|
358
|
-
* `
|
|
359
|
-
* adapter, and the conformance harness names the member
|
|
360
|
-
* rather than throwing from inside itself.
|
|
393
|
+
* {@link CONTROL_PLANE_SCHEMA} alone. `UNRENDERED_FIELDS`, `NATIVE_ASK_TIER`,
|
|
394
|
+
* `PRE_TOOL_FILING` and `PRE_TOOL_RESPONSE` are such members: adding one is a
|
|
395
|
+
* one-line change per adapter, and the conformance harness names the member
|
|
396
|
+
* and the migration rather than throwing from inside itself.
|
|
361
397
|
*/
|
|
362
398
|
/** Wire identifier for this schema version; bump on a breaking shape change. */
|
|
363
399
|
export const CONTROL_PLANE_SCHEMA: "control-plane/v1";
|
|
@@ -544,6 +580,14 @@ export const VERDICT_CONTENT_FIELDS: readonly string[];
|
|
|
544
580
|
* ignores the key it was written into.
|
|
545
581
|
*/
|
|
546
582
|
export const UNRENDERED_ON_UNKNOWN: ReadonlySet<string>;
|
|
583
|
+
/** The {@link PreToolFiling} input reference to a call's whole arguments value. */
|
|
584
|
+
export const WHOLE_ARGUMENTS: "$";
|
|
585
|
+
/** The prefix of a {@link PreToolFiling} input reference to one top-level argument. */
|
|
586
|
+
export const ARGUMENT_KEY_PREFIX: "$.";
|
|
587
|
+
/** What a {@link PreToolFiling} rule must be, for error messages. */
|
|
588
|
+
export const FILING_RULE_GRAMMAR: "{tool_name: <non-empty string>, input: \"$\" or an object of \"$\"/\"$.<key>\" references (<key>: non-empty, no \".\")}";
|
|
589
|
+
/** The kinds of model tool call a {@link PreToolFiling} distinguishes. */
|
|
590
|
+
export const MODEL_CALL_KINDS: ReadonlySet<string>;
|
|
547
591
|
/**
|
|
548
592
|
* PROBLEM CLASS — an event marked vetoable on a path where the host can never
|
|
549
593
|
* veto it, so a deny reads downstream as a block that never happened.
|
|
@@ -590,6 +634,8 @@ export const DEFAULT_DENY_REASON: "blocked by monitor";
|
|
|
590
634
|
* exit. Every host here discards (or never gets) the stdout body on exit 2.
|
|
591
635
|
* - `reason_required`: the host drops a block whose reason is empty and runs
|
|
592
636
|
* the tool, so every body field that carries the reason must be non-empty.
|
|
637
|
+
* Unlike the other two fields, this holds for ANY block body the host parses,
|
|
638
|
+
* enforced or advisory: Codex voids an empty-reason deny either way.
|
|
593
639
|
*
|
|
594
640
|
* {@link gatedResponse} derives each adapter's enforced render from its row,
|
|
595
641
|
* and the conformance harness holds every enforced render to the same row.
|
|
@@ -600,6 +646,21 @@ export const BLOCK_TRANSPORT: Readonly<Record<string, Readonly<{
|
|
|
600
646
|
reason_stream: "stderr";
|
|
601
647
|
reason_required: boolean;
|
|
602
648
|
}>>>;
|
|
649
|
+
/**
|
|
650
|
+
* What a host reads from an exit-0 answer to its pre-tool event (`PreToolUse`,
|
|
651
|
+
* `BeforeTool`), as two sorted key lists, each empty when the host reads no
|
|
652
|
+
* body: `hook_specific_output_keys` inside `hookSpecificOutput`, and
|
|
653
|
+
* `top_level_keys` at the body's top level. Each list has a fail-open bit,
|
|
654
|
+
* `unread_key_fails_open` and `unread_top_level_key_fails_open`: `true` ⇒ any
|
|
655
|
+
* other key there voids the whole body and the call RUNS; `false` ⇒ other keys
|
|
656
|
+
* are ignored; `null` ⇒ not established from the host's source.
|
|
657
|
+
*/
|
|
658
|
+
export type PreToolResponse = Readonly<{
|
|
659
|
+
hook_specific_output_keys: readonly string[];
|
|
660
|
+
unread_key_fails_open: boolean | null;
|
|
661
|
+
top_level_keys: readonly string[];
|
|
662
|
+
unread_top_level_key_fails_open: boolean | null;
|
|
663
|
+
}>;
|
|
603
664
|
/**
|
|
604
665
|
* The translator for one agent's protocol. `parse` maps a native event to a
|
|
605
666
|
* {@link ToolCallEvent} (never throwing on unmodelled input, stamping the
|
|
@@ -626,7 +687,15 @@ export type Adapter = {
|
|
|
626
687
|
*/
|
|
627
688
|
NATIVE_ASK_TIER: boolean;
|
|
628
689
|
/**
|
|
629
|
-
*
|
|
690
|
+
* how this host's pre-tool hook files a model tool call, for a consumer matching the model's calls against the hook's. `{}` when no rename is modelled.
|
|
691
|
+
*/
|
|
692
|
+
PRE_TOOL_FILING: PreToolFiling;
|
|
693
|
+
/**
|
|
694
|
+
* the top-level and `hookSpecificOutput` keys this host reads on its pre-tool event, each list with whether an unread key there runs the call. The conformance harness holds every pre-tool render to both lists.
|
|
695
|
+
*/
|
|
696
|
+
PRE_TOOL_RESPONSE: PreToolResponse;
|
|
697
|
+
/**
|
|
698
|
+
* per-event-kind set of {@link VERDICT_CONTENT_FIELDS} this host has no native channel for, so `render` drops them. EVERY {@link EventKind} carries a row, including one this adapter's `parse` cannot emit — an omission would otherwise read as "every content field reaches a channel here", the reverse of the truth on a transport that carries none. The value type still admits `undefined` so a consumer handles a lookup miss rather than indexing straight into `.has(...)`; the conformance harness refuses a missing row. SCOPE — a row describes the ALLOW path on the kind's `NATIVE_EVENT_FOR` event only; another native event of that kind (Codex's `PermissionRequest`) may drop more. An enforceable deny may drop more: Gemini's exit-2 System Block returns no stdout at all, so a deny there carries no `additional_context` whatever the row says. Do not read this map to infer what a deny delivers. The conformance harness fails an adapter whose renders disagree with its declaration either way, so a stale entry cannot survive.
|
|
630
699
|
*/
|
|
631
700
|
UNRENDERED_FIELDS: Record<string, ReadonlySet<string> | undefined>;
|
|
632
701
|
};
|
|
@@ -739,3 +808,32 @@ export type Verdict = {
|
|
|
739
808
|
*/
|
|
740
809
|
reason?: string | undefined;
|
|
741
810
|
};
|
|
811
|
+
/**
|
|
812
|
+
* How a host's pre-tool hook files a model's tool call, keyed by MODEL tool
|
|
813
|
+
* name, the {@link flatToolName} of the call's `namespace` and `name`. Each
|
|
814
|
+
* value is one of:
|
|
815
|
+
* - `null`: no pre-tool hook fires for this call.
|
|
816
|
+
* - `{ tool_name, input }`: the hook fires with `tool_name`, and `tool_input`
|
|
817
|
+
* built from `input` — either `"$"` (the call's whole arguments value), or
|
|
818
|
+
* an object whose every value is `"$"` or `"$.<key>"`. `<key>` is one
|
|
819
|
+
* literal, non-empty top-level key with no `.` in it. The hook files NOTHING
|
|
820
|
+
* when a `"$.<key>"` does not resolve: the arguments are not an object, lack
|
|
821
|
+
* the key, or hold `null` there.
|
|
822
|
+
* A FUNCTION call the map does not list is filed as the model asked: same
|
|
823
|
+
* name, whole arguments. A FREEFORM (custom) call the map does not list files
|
|
824
|
+
* nothing. Of the shipped hosts only Codex makes freeform calls ([X7] in
|
|
825
|
+
* `docs/hook-coverage-matrix.md`).
|
|
826
|
+
* The map describes a call the host DISPATCHES. Codex files nothing for a call
|
|
827
|
+
* it rejects first: a name it has no tool for, a call kind the tool does not
|
|
828
|
+
* take (each listed rule assumes the tool's own kind), or arguments the
|
|
829
|
+
* handler refuses before its hook (`apply_patch` whose patch does not parse).
|
|
830
|
+
* On Codex the ARGUMENTS VALUE is a function call's `arguments` parsed as JSON
|
|
831
|
+
* (an empty or all-whitespace string reads as `{}`, unparseable text stays the
|
|
832
|
+
* raw string), or a freeform call's raw `input` string.
|
|
833
|
+
* Plain JSON by design: `agents.json` publishes it so a consumer with no JS
|
|
834
|
+
* runtime applies it the way {@link fileModelCall} does.
|
|
835
|
+
*/
|
|
836
|
+
export type PreToolFiling = Readonly<Record<string, null | Readonly<{
|
|
837
|
+
tool_name: string;
|
|
838
|
+
input: string | Readonly<Record<string, string>>;
|
|
839
|
+
}>>>;
|