@retinue/agentkit 0.1.0 → 0.2.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 +59 -277
- package/dist/adapters/embeddings/openai.d.ts +45 -0
- package/dist/adapters/embeddings/openai.js +109 -0
- package/dist/agents/agent.d.ts +22 -1
- package/dist/agents/agent.js +97 -11
- package/dist/agents/engine.d.ts +28 -0
- package/dist/agents/engine.js +194 -8
- package/dist/capabilities/index.d.ts +5 -1
- package/dist/capabilities/index.js +23 -0
- package/dist/capabilities/runtime.d.ts +8 -0
- package/dist/core/budget.d.ts +55 -0
- package/dist/core/budget.js +56 -0
- package/dist/core/content-parts.d.ts +8 -0
- package/dist/core/events.d.ts +68 -2
- package/dist/core/events.js +2 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/documents/index.d.ts +14 -0
- package/dist/documents/parsers/text.d.ts +16 -0
- package/dist/documents/parsers/text.js +54 -2
- package/dist/entries/guardrails.d.ts +14 -0
- package/dist/entries/guardrails.js +14 -0
- package/dist/entries/knowledge.d.ts +9 -0
- package/dist/entries/knowledge.js +8 -0
- package/dist/graphql/resolvers.d.ts +4 -0
- package/dist/graphql/resolvers.js +6 -0
- package/dist/graphql/schema.d.ts +1 -1
- package/dist/graphql/schema.js +44 -0
- package/dist/guardrails/index.d.ts +115 -0
- package/dist/guardrails/index.js +108 -0
- package/dist/guardrails/moderation.d.ts +53 -0
- package/dist/guardrails/moderation.js +75 -0
- package/dist/guardrails/pii.d.ts +75 -0
- package/dist/guardrails/pii.js +193 -0
- package/dist/knowledge/index.d.ts +1 -0
- package/dist/knowledge/index.js +1 -0
- package/dist/knowledge/navigate.d.ts +89 -0
- package/dist/knowledge/navigate.js +107 -0
- package/dist/knowledge/retrieval.d.ts +73 -5
- package/dist/knowledge/retrieval.js +82 -28
- package/dist/models/streaming.d.ts +22 -1
- package/dist/models/streaming.js +5 -1
- package/dist/security/checklist.js +9 -0
- package/dist/security/findings.js +18 -9
- package/dist/skills/catalogue.d.ts +49 -0
- package/dist/skills/catalogue.js +61 -0
- package/dist/skills/index.d.ts +1 -0
- package/dist/skills/index.js +1 -0
- package/dist/telemetry/spans.js +12 -0
- package/dist/toolkit/files.d.ts +125 -0
- package/dist/toolkit/files.js +320 -0
- package/dist/toolkit/index.d.ts +4 -0
- package/dist/toolkit/index.js +2 -0
- package/dist/toolkit/sandbox.d.ts +119 -0
- package/dist/toolkit/sandbox.js +239 -0
- package/dist/toolkit/web.d.ts +13 -0
- package/dist/toolkit/web.js +7 -1
- package/dist/tools/budget.d.ts +28 -0
- package/dist/tools/budget.js +35 -0
- package/dist/tools/credentials.d.ts +57 -0
- package/dist/tools/credentials.js +54 -0
- package/dist/tools/define.d.ts +31 -0
- package/dist/tools/define.js +23 -0
- package/dist/tools/find.d.ts +109 -0
- package/dist/tools/find.js +210 -0
- package/dist/tools/index.d.ts +14 -2
- package/dist/tools/index.js +4 -0
- package/dist/tools/library/fs.d.ts +24 -0
- package/dist/tools/library/fs.js +102 -0
- package/dist/tools/library/index.d.ts +29 -2
- package/dist/tools/library/index.js +40 -0
- package/dist/tools/library/shell.d.ts +45 -0
- package/dist/tools/library/shell.js +70 -0
- package/dist/tools/meta-tools.js +8 -0
- package/dist/tools/registry.d.ts +113 -0
- package/dist/tools/registry.js +180 -4
- package/package.json +5 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A token ceiling on a catalogue, and the rule that truncation is never quiet — REQ-045 (#204), task #210.
|
|
3
|
+
*
|
|
4
|
+
* The tool catalogue and the skill catalogue have the same problem: a compact entry per item, resident on every
|
|
5
|
+
* turn, linear in how many exist. #221 measured ~35 tokens per tool entry, so 200 tools is ~7,000 tokens before
|
|
6
|
+
* a word of the conversation. Two-tier loading bounded the *schemas* and left the entries.
|
|
7
|
+
*
|
|
8
|
+
* ## Truncation must be loud, and this is the whole guarantee
|
|
9
|
+
*
|
|
10
|
+
* A silently shortened catalogue is **indistinguishable from correct behaviour**. The model is not told an item
|
|
11
|
+
* was withheld; it simply never uses it, and the transcript reads as a run where the model chose not to. Nobody
|
|
12
|
+
* reviewing that run has anything to notice. So dropping is never quiet: the outcome names every dropped item,
|
|
13
|
+
* the engine turns that into a run event, and #210's AC-7 is a test that removing the event fails the build.
|
|
14
|
+
*
|
|
15
|
+
* In `core/` because both callers need it and neither owns it — and because a second copy for skills would drift
|
|
16
|
+
* from the first, which is the shape this repository keeps finding defects in.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Keep what fits, in the order given, and name what did not.
|
|
20
|
+
*
|
|
21
|
+
* **Order is the caller's priority**, not a ranking invented here. For tools that is the order the host's
|
|
22
|
+
* providers were registered in, which is a deployment's own statement about what matters; inventing a relevance
|
|
23
|
+
* order would mean guessing at the model's next need, and `find_tools` is the honest answer to that.
|
|
24
|
+
*
|
|
25
|
+
* `protect` names what may never be dropped. Meta-tools are the reason it exists: dropping `find_tools` or
|
|
26
|
+
* `learn_tools` to save 35 tokens would remove the model's only route back to everything else, turning a budget
|
|
27
|
+
* into a permanent amputation.
|
|
28
|
+
*/
|
|
29
|
+
export const applyTokenBudget = (input) => {
|
|
30
|
+
const protectedItems = [];
|
|
31
|
+
const droppable = [];
|
|
32
|
+
for (const item of input.items)
|
|
33
|
+
(input.protect?.(item) === true ? protectedItems : droppable).push(item);
|
|
34
|
+
// Protected first, and counted even when they overrun: a report that omitted them would understate the
|
|
35
|
+
// resident cost and hide precisely the misconfiguration `overBudget` exists to name.
|
|
36
|
+
let spent = protectedItems.reduce((total, item) => total + input.tokensOf(item), 0);
|
|
37
|
+
const resident = [...protectedItems];
|
|
38
|
+
const dropped = [];
|
|
39
|
+
for (const item of droppable) {
|
|
40
|
+
const cost = input.tokensOf(item);
|
|
41
|
+
if (spent + cost <= input.budget.maxTokens) {
|
|
42
|
+
resident.push(item);
|
|
43
|
+
spent += cost;
|
|
44
|
+
}
|
|
45
|
+
else
|
|
46
|
+
dropped.push(input.nameOf(item));
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
resident,
|
|
50
|
+
dropped,
|
|
51
|
+
residentTokens: spent,
|
|
52
|
+
budgetTokens: input.budget.maxTokens,
|
|
53
|
+
overBudget: spent > input.budget.maxTokens,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -34,6 +34,14 @@ export type ToolCallPart = PartBase<"tool-call"> & {
|
|
|
34
34
|
export type ToolResultPart = PartBase<"tool-result"> & {
|
|
35
35
|
readonly toolCallId: ToolCallId;
|
|
36
36
|
readonly toolName: string;
|
|
37
|
+
/**
|
|
38
|
+
* What actually ran, when it differs from `toolName` — task #210.
|
|
39
|
+
*
|
|
40
|
+
* `execute_tool` names its target, so the call the model made and the action performed are two different
|
|
41
|
+
* things. Both are recorded: the first is what the model's own history says it did, and the second is what a
|
|
42
|
+
* reader needs.
|
|
43
|
+
*/
|
|
44
|
+
readonly ranToolName?: string;
|
|
37
45
|
/** Populated for inline results. Large results are spilled and referenced instead. */
|
|
38
46
|
readonly output?: unknown;
|
|
39
47
|
/** Authorized reference to an offloaded result, read back via `read_tool_output`. */
|
package/dist/core/events.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { MessagePart } from "./content-parts.js";
|
|
9
9
|
import type { PlatformError } from "./errors.js";
|
|
10
10
|
import type { InteractionId, MessageId, RunId, TenantId, ToolCallId } from "./ids.js";
|
|
11
|
-
export declare const RUN_EVENT_TYPES: readonly ["run.queued", "run.started", "run.checkpointed", "run.completed", "run.failed", "run.cancelled", "run.retry-pending", "part.added", "part.updated", "tool.started", "tool.completed", "tool.failed", "question.requested", "question.answered", "approval.requested", "approval.decided", "usage.updated", "context.compacted"];
|
|
11
|
+
export declare const RUN_EVENT_TYPES: readonly ["run.queued", "run.started", "run.checkpointed", "run.completed", "run.failed", "run.cancelled", "run.retry-pending", "part.added", "part.updated", "tool.started", "tool.completed", "tool.failed", "question.requested", "question.answered", "approval.requested", "approval.decided", "usage.updated", "context.compacted", "guardrail.verdict", "catalog.truncated"];
|
|
12
12
|
export type RunEventType = (typeof RUN_EVENT_TYPES)[number];
|
|
13
13
|
type EventBase<T extends RunEventType> = {
|
|
14
14
|
readonly type: T;
|
|
@@ -41,6 +41,14 @@ export type PartEvent = EventBase<"part.added" | "part.updated"> & {
|
|
|
41
41
|
export type ToolEvent = EventBase<"tool.started" | "tool.completed" | "tool.failed"> & {
|
|
42
42
|
readonly toolCallId: ToolCallId;
|
|
43
43
|
readonly toolName: string;
|
|
44
|
+
/**
|
|
45
|
+
* The tool that actually ran, when the model called `execute_tool` — task #210.
|
|
46
|
+
*
|
|
47
|
+
* Absent on `tool.started`, because at that point nothing has resolved the target yet. Present on completion
|
|
48
|
+
* and failure, where it is the difference between an audit trail that names an action and one that names a
|
|
49
|
+
* mechanism.
|
|
50
|
+
*/
|
|
51
|
+
readonly ranToolName?: string;
|
|
44
52
|
};
|
|
45
53
|
export type InteractionEvent = EventBase<"question.requested" | "question.answered" | "approval.requested" | "approval.decided"> & {
|
|
46
54
|
readonly interactionId: InteractionId;
|
|
@@ -70,7 +78,65 @@ export type ContextCompactedEvent = EventBase<"context.compacted"> & {
|
|
|
70
78
|
readonly droppedParts: number;
|
|
71
79
|
readonly tokensReclaimed: number;
|
|
72
80
|
};
|
|
73
|
-
|
|
81
|
+
/**
|
|
82
|
+
* What a guardrail concluded — REQ-046 (#205).
|
|
83
|
+
*
|
|
84
|
+
* Emitted for every verdict, including a pass, because "no guardrail ran" and "a guardrail ran and allowed it"
|
|
85
|
+
* are different facts and an incident review needs to tell them apart.
|
|
86
|
+
*
|
|
87
|
+
* **Carries no inspected value, ever.** `what` names fields or entity types; the content that was redacted is
|
|
88
|
+
* precisely what must not travel into an event log, a trace, or a support ticket — otherwise the audit trail
|
|
89
|
+
* becomes the leak it exists to record.
|
|
90
|
+
*/
|
|
91
|
+
export type GuardrailVerdictEvent = EventBase<"guardrail.verdict"> & {
|
|
92
|
+
readonly guardrail: string;
|
|
93
|
+
readonly subject: "input" | "message" | "tool-call" | "tool-result";
|
|
94
|
+
readonly outcome: "pass" | "redacted" | "refused";
|
|
95
|
+
/** For a redaction: the fields or entity types touched. Never their contents. */
|
|
96
|
+
readonly what?: readonly string[];
|
|
97
|
+
/** For a refusal. */
|
|
98
|
+
readonly code?: string;
|
|
99
|
+
/** True when the guardrail threw and was therefore treated as a refusal. */
|
|
100
|
+
readonly threw?: boolean;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* A catalogue did not fit its budget, and what was left out — REQ-045 (#204), task #210, AC-3 and AC-5.
|
|
104
|
+
*
|
|
105
|
+
* This event *is* the guarantee. A truncated tool list is invisible from inside a run: the model is not told a
|
|
106
|
+
* tool was withheld, so it never calls it, and the transcript reads exactly like a run where the model chose
|
|
107
|
+
* not to. Every other failure at least looks like a failure; this one looks like a decision. So the names go in
|
|
108
|
+
* the log, in full, and #210's AC-7 is a test that deleting this event fails the build.
|
|
109
|
+
*
|
|
110
|
+
* Names, not a count. "14 tools were dropped" tells a reader that something happened and nothing about whether
|
|
111
|
+
* it mattered; `github_merge_pull_request` tells them immediately.
|
|
112
|
+
*/
|
|
113
|
+
export type CatalogTruncatedEvent = EventBase<"catalog.truncated"> & {
|
|
114
|
+
/**
|
|
115
|
+
* Which catalogue. One event rather than two, because the fact is the same fact and a reader asking "what was
|
|
116
|
+
* withheld from this turn" should not have to know there are two mechanisms.
|
|
117
|
+
*/
|
|
118
|
+
readonly catalog: "tools" | "skills";
|
|
119
|
+
readonly budgetTokens: number;
|
|
120
|
+
readonly residentTokens: number;
|
|
121
|
+
/** Every dropped tool, by name. */
|
|
122
|
+
readonly dropped: readonly string[];
|
|
123
|
+
/**
|
|
124
|
+
* Whether the model can still get to what was dropped.
|
|
125
|
+
*
|
|
126
|
+
* `find_tools` makes a truncated tool catalogue a *deferral*; without it the same event describes a permanent
|
|
127
|
+
* removal, and a reader cannot tell which from the names alone. There is no `find_skills`, so a truncated
|
|
128
|
+
* skill catalogue is always `false` today — which is a fact worth having in the log rather than a field to
|
|
129
|
+
* leave off.
|
|
130
|
+
*/
|
|
131
|
+
readonly findable: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Set when the protected set alone exceeds the budget — a misconfiguration, not the mechanism working.
|
|
134
|
+
*
|
|
135
|
+
* A deployment in this state believes it capped its context and has not.
|
|
136
|
+
*/
|
|
137
|
+
readonly overBudget?: boolean;
|
|
138
|
+
};
|
|
139
|
+
export type RunEvent = RunLifecycleEvent | RunFailedEvent | RunRetryPendingEvent | PartEvent | ToolEvent | InteractionEvent | UsageUpdatedEvent | ContextCompactedEvent | GuardrailVerdictEvent | CatalogTruncatedEvent;
|
|
74
140
|
/** Fan-out port. Adapters: Supabase Realtime, Redis pub/sub, in-memory for tests. */
|
|
75
141
|
export interface RealtimePublisher {
|
|
76
142
|
publish(channel: string, event: RunEvent): Promise<void>;
|
package/dist/core/events.js
CHANGED
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
|
@@ -73,6 +73,20 @@ export type ExtractedDocument = {
|
|
|
73
73
|
* answer, and a log line does not reach them.
|
|
74
74
|
*/
|
|
75
75
|
readonly warnings: readonly string[];
|
|
76
|
+
/**
|
|
77
|
+
* YAML front matter, when the source had a leading `---` block — REQ-050 (#209), task #220.
|
|
78
|
+
*
|
|
79
|
+
* **Scalar keys only, and deliberately not a YAML parser.** A nested mapping, a list of mappings or a flow
|
|
80
|
+
* collection is reported in `warnings` and otherwise dropped, because a half-correct parse of provenance
|
|
81
|
+
* metadata is worse than none: `generated: { by: x, at: y }` silently read as the string `{ by: x, at: y }`
|
|
82
|
+
* would be recorded as if it had been understood.
|
|
83
|
+
*
|
|
84
|
+
* It exists because the alternative was worse. Front matter used to reach the block stream as *content*: a
|
|
85
|
+
* document's `sidebar_position` and `type` became a paragraph, got chunked, embedded, and could be returned as
|
|
86
|
+
* a retrieval hit and cited. #220 found it while reading the Open Knowledge Format — whose concept files carry
|
|
87
|
+
* far more metadata than ours — and it was already happening to this repository's own documentation site.
|
|
88
|
+
*/
|
|
89
|
+
readonly frontMatter?: Readonly<Record<string, string>>;
|
|
76
90
|
/**
|
|
77
91
|
* How confident the extraction is, 0–1 (#132).
|
|
78
92
|
*
|
|
@@ -30,6 +30,22 @@ export type BlockBuilder = ReturnType<typeof createBlockBuilder>;
|
|
|
30
30
|
* Plain text has no headings or tables, so running it through the same parser costs nothing and means one
|
|
31
31
|
* code path instead of two that differ in how they split paragraphs.
|
|
32
32
|
*/
|
|
33
|
+
/**
|
|
34
|
+
* A leading `---` block, split off before anything else reads the document — task #220.
|
|
35
|
+
*
|
|
36
|
+
* Returns the remaining lines and the scalar keys, and **names what it could not read** rather than guessing.
|
|
37
|
+
* Front matter is metadata about a document, not part of it: left in the stream it becomes a paragraph, gets
|
|
38
|
+
* chunked and embedded, and can be returned as a retrieval hit — so a question about revenue policy can be
|
|
39
|
+
* answered with a block of YAML.
|
|
40
|
+
*
|
|
41
|
+
* Scalar keys only. `generated: { by: x, at: y }` read as the *string* `{ by: x, at: y }` would be provenance
|
|
42
|
+
* recorded as if it had been understood, which is worse than a warning saying it was skipped.
|
|
43
|
+
*/
|
|
44
|
+
export declare const splitFrontMatter: (lines: readonly string[]) => {
|
|
45
|
+
readonly body: readonly string[];
|
|
46
|
+
readonly fields: Record<string, string>;
|
|
47
|
+
readonly warnings: readonly string[];
|
|
48
|
+
};
|
|
33
49
|
export declare const parseMarkdown: (bytes: Uint8Array, limits: ExtractionLimits) => ExtractedDocument;
|
|
34
50
|
/**
|
|
35
51
|
* A CSV row splitter that understands quotes.
|
|
@@ -96,9 +96,56 @@ const isPipeDivider = (line) => /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s
|
|
|
96
96
|
* Plain text has no headings or tables, so running it through the same parser costs nothing and means one
|
|
97
97
|
* code path instead of two that differ in how they split paragraphs.
|
|
98
98
|
*/
|
|
99
|
+
/**
|
|
100
|
+
* A leading `---` block, split off before anything else reads the document — task #220.
|
|
101
|
+
*
|
|
102
|
+
* Returns the remaining lines and the scalar keys, and **names what it could not read** rather than guessing.
|
|
103
|
+
* Front matter is metadata about a document, not part of it: left in the stream it becomes a paragraph, gets
|
|
104
|
+
* chunked and embedded, and can be returned as a retrieval hit — so a question about revenue policy can be
|
|
105
|
+
* answered with a block of YAML.
|
|
106
|
+
*
|
|
107
|
+
* Scalar keys only. `generated: { by: x, at: y }` read as the *string* `{ by: x, at: y }` would be provenance
|
|
108
|
+
* recorded as if it had been understood, which is worse than a warning saying it was skipped.
|
|
109
|
+
*/
|
|
110
|
+
export const splitFrontMatter = (lines) => {
|
|
111
|
+
if ((lines[0] ?? "").trim() !== "---")
|
|
112
|
+
return { body: lines, fields: {}, warnings: [] };
|
|
113
|
+
const end = lines.findIndex((line, at) => at > 0 && line.trim() === "---");
|
|
114
|
+
// No closing fence: a document that happens to start with a horizontal rule, not front matter.
|
|
115
|
+
if (end === -1)
|
|
116
|
+
return { body: lines, fields: {}, warnings: [] };
|
|
117
|
+
const fields = {};
|
|
118
|
+
const skipped = [];
|
|
119
|
+
for (const line of lines.slice(1, end)) {
|
|
120
|
+
if (line.trim() === "" || line.trimStart().startsWith("#"))
|
|
121
|
+
continue;
|
|
122
|
+
// An indented line or a list item belongs to a structure this does not read.
|
|
123
|
+
if (/^\s/.test(line) || line.trimStart().startsWith("- "))
|
|
124
|
+
continue;
|
|
125
|
+
const match = /^([A-Za-z_][\w.-]*)\s*:\s*(.*)$/.exec(line);
|
|
126
|
+
if (match === null)
|
|
127
|
+
continue;
|
|
128
|
+
const [, key, raw] = match;
|
|
129
|
+
const value = (raw ?? "").trim();
|
|
130
|
+
// A flow collection or the start of a block one: named as skipped, never half-read.
|
|
131
|
+
if (value === "" || value.startsWith("{") || value.startsWith("[")) {
|
|
132
|
+
skipped.push(key);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
fields[key] = value.replace(/^["']|["']$/g, "");
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
body: lines.slice(end + 1),
|
|
139
|
+
fields,
|
|
140
|
+
warnings: skipped.length === 0
|
|
141
|
+
? []
|
|
142
|
+
: [`front matter key(s) not read because they are not scalars: ${skipped.join(", ")}`],
|
|
143
|
+
};
|
|
144
|
+
};
|
|
99
145
|
export const parseMarkdown = (bytes, limits) => {
|
|
100
146
|
const builder = createBlockBuilder(limits);
|
|
101
|
-
const
|
|
147
|
+
const matter = splitFrontMatter(decode(bytes).split(/\r?\n/));
|
|
148
|
+
const lines = matter.body;
|
|
102
149
|
let paragraph = [];
|
|
103
150
|
const flushParagraph = () => {
|
|
104
151
|
if (paragraph.length === 0)
|
|
@@ -176,7 +223,12 @@ export const parseMarkdown = (bytes, limits) => {
|
|
|
176
223
|
}
|
|
177
224
|
flushParagraph();
|
|
178
225
|
flushList();
|
|
179
|
-
|
|
226
|
+
const parsed = builder.done();
|
|
227
|
+
return {
|
|
228
|
+
...parsed,
|
|
229
|
+
...(Object.keys(matter.fields).length === 0 ? {} : { frontMatter: matter.fields }),
|
|
230
|
+
warnings: [...parsed.warnings, ...matter.warnings],
|
|
231
|
+
};
|
|
180
232
|
};
|
|
181
233
|
/**
|
|
182
234
|
* A CSV row splitter that understands quotes.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@retinue/agentkit/guardrails` — checks a deployment adds, and the contract they satisfy.
|
|
3
|
+
*
|
|
4
|
+
* Its own subpath rather than part of `./hitl`, though both stop a run. The distinction is who decides: a
|
|
5
|
+
* guardrail is an automated inspection whose verdict is final, and HITL is a person being asked. Putting them
|
|
6
|
+
* together would invite a host to treat a refusal as something a human could override, which is precisely what a
|
|
7
|
+
* guardrail must not be.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here imports a provider or a store, so declaring the capability costs a consumer no dependency.
|
|
10
|
+
*/
|
|
11
|
+
export * from "../guardrails/index.js";
|
|
12
|
+
export * from "../guardrails/pii.js";
|
|
13
|
+
export * from "../guardrails/moderation.js";
|
|
14
|
+
//# sourceMappingURL=guardrails.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@retinue/agentkit/guardrails` — checks a deployment adds, and the contract they satisfy.
|
|
3
|
+
*
|
|
4
|
+
* Its own subpath rather than part of `./hitl`, though both stop a run. The distinction is who decides: a
|
|
5
|
+
* guardrail is an automated inspection whose verdict is final, and HITL is a person being asked. Putting them
|
|
6
|
+
* together would invite a host to treat a refusal as something a human could override, which is precisely what a
|
|
7
|
+
* guardrail must not be.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here imports a provider or a store, so declaring the capability costs a consumer no dependency.
|
|
10
|
+
*/
|
|
11
|
+
export * from "../guardrails/index.js";
|
|
12
|
+
export * from "../guardrails/pii.js";
|
|
13
|
+
export * from "../guardrails/moderation.js";
|
|
14
|
+
//# sourceMappingURL=guardrails.js.map
|
|
@@ -15,4 +15,13 @@ export * from "../artifacts/index.js";
|
|
|
15
15
|
export * from "../export/index.js";
|
|
16
16
|
export * from "../export/pdf.js";
|
|
17
17
|
export * from "../export/markdown.js";
|
|
18
|
+
/**
|
|
19
|
+
* The one embedding adapter — REQ-050 (#209), task #219.
|
|
20
|
+
*
|
|
21
|
+
* Here rather than behind an `adapters/*` subpath because it carries no driver: it is a `fetch` to an
|
|
22
|
+
* OpenAI-shaped endpoint, so it adds nothing to a consumer's install. The Postgres and Redis adapters have their
|
|
23
|
+
* own subpaths because they each pull a client library in.
|
|
24
|
+
*/
|
|
25
|
+
export { DEFAULT_EMBEDDING_BATCH, DEFAULT_EMBEDDING_MODEL, createOpenAiEmbeddings } from "../adapters/embeddings/openai.js";
|
|
26
|
+
export type { OpenAiEmbeddingsConfig } from "../adapters/embeddings/openai.js";
|
|
18
27
|
//# sourceMappingURL=knowledge.d.ts.map
|
|
@@ -16,4 +16,12 @@ export * from "../artifacts/index.js";
|
|
|
16
16
|
export * from "../export/index.js";
|
|
17
17
|
export * from "../export/pdf.js";
|
|
18
18
|
export * from "../export/markdown.js";
|
|
19
|
+
/**
|
|
20
|
+
* The one embedding adapter — REQ-050 (#209), task #219.
|
|
21
|
+
*
|
|
22
|
+
* Here rather than behind an `adapters/*` subpath because it carries no driver: it is a `fetch` to an
|
|
23
|
+
* OpenAI-shaped endpoint, so it adds nothing to a consumer's install. The Postgres and Redis adapters have their
|
|
24
|
+
* own subpaths because they each pull a client library in.
|
|
25
|
+
*/
|
|
26
|
+
export { DEFAULT_EMBEDDING_BATCH, DEFAULT_EMBEDDING_MODEL, createOpenAiEmbeddings } from "../adapters/embeddings/openai.js";
|
|
19
27
|
//# sourceMappingURL=knowledge.js.map
|
|
@@ -76,6 +76,10 @@ export declare const createResolvers: (deps: ResolverDeps) => {
|
|
|
76
76
|
categories: string[];
|
|
77
77
|
excluded: string[];
|
|
78
78
|
}, ctx: GraphQLContext): Promise<import("../tools/registry.js").ToolCatalog>;
|
|
79
|
+
findTools(_: unknown, args: {
|
|
80
|
+
query: string;
|
|
81
|
+
limit?: number;
|
|
82
|
+
}, ctx: GraphQLContext): Promise<import("../tools/find.js").ToolSearchOutcome>;
|
|
79
83
|
usage(_: unknown, args: {
|
|
80
84
|
runId?: string;
|
|
81
85
|
}, ctx: GraphQLContext): Promise<import("../persistence/index.js").UsageTotals>;
|
|
@@ -75,6 +75,12 @@ export const createResolvers = (deps) => {
|
|
|
75
75
|
async toolCatalog(_, args, ctx) {
|
|
76
76
|
return deps.toolRegistry.catalog(ctx.execution, args);
|
|
77
77
|
},
|
|
78
|
+
async findTools(_, args, ctx) {
|
|
79
|
+
return deps.toolRegistry.find(ctx.execution, {
|
|
80
|
+
query: args.query,
|
|
81
|
+
...(args.limit === undefined ? {} : { limit: args.limit }),
|
|
82
|
+
});
|
|
83
|
+
},
|
|
78
84
|
async usage(_, args, ctx) {
|
|
79
85
|
return deps.usage.totals({ tenantId: tid(ctx), ...(args.runId ? { runId: asId(args.runId) } : {}) });
|
|
80
86
|
},
|
package/dist/graphql/schema.d.ts
CHANGED
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
* platform service. Subscriptions carry the stable `RunEvent` set and support resuming after a
|
|
8
8
|
* cursor via `openRunEventStream`.
|
|
9
9
|
*/
|
|
10
|
-
export declare const typeDefs = "\n scalar JSON\n scalar DateTime\n\n type Conversation {\n id: ID!\n title: String!\n version: Int!\n archivedAt: DateTime\n createdAt: DateTime!\n updatedAt: DateTime!\n }\n\n type ConversationPage {\n items: [Conversation!]!\n nextCursor: String\n }\n\n enum RunStatus {\n queued\n running\n waiting_for_question\n waiting_for_approval\n retry_pending\n completed\n failed\n cancelled\n }\n\n type Run {\n id: ID!\n \"Null for a run that belongs to no conversation \u2014 a triggered automation rather than a chat turn (#198).\"\n conversationId: ID\n status: RunStatus!\n createdAt: DateTime!\n finishedAt: DateTime\n }\n\n type ToolCatalogEntry {\n name: String!\n label: String!\n description: String!\n category: String!\n effect: String!\n }\n\n type ToolCatalog {\n preloaded: [JSON!]!\n discoverable: [ToolCatalogEntry!]!\n meta: [ToolCatalogEntry!]!\n }\n\n type UsageTotals {\n inputTokens: Int!\n outputTokens: Int!\n cachedInputTokens: Int!\n costMinorUnits: Int!\n eventCount: Int!\n }\n\n \"One period's consumption. #139's rollups, so a chart never scans raw records.\"\n type UsageBucket {\n bucketStart: String!\n totals: UsageTotals!\n currency: String!\n }\n\n \"Consumption grouped by model or conversation over the requested range.\"\n type UsageBreakdownEntry {\n key: String!\n totals: UsageTotals!\n }\n\n \"\"\"\n Where the tenant stands against its limit.\n\n Absent when no limit is configured \u2014 which means unbounded, not zero. A UI must show \"no limit set\"\n rather than a full bar.\n \"\"\"\n type UsageQuota {\n \"\"\"\n The window in words \u2014 \"the day\", \"any 5 hours\" (#181).\n\n period stayed for the calendar case and is **null** for a rolling window, because no RollupPeriod describes\n one and returning \"hour\" for a five-hour window would be a wrong answer rather than a missing one. A client\n rendering the window reads this field; one keying a chart by bucket reads period and correctly finds nothing\n to key by.\n \"\"\"\n window: String!\n period: String\n \"The model this limit covers, or null for any model (#182).\"\n modelId: String\n costLimitMinorUnits: Int\n inputTokenLimit: Int\n outputTokenLimit: Int\n \"The fraction of the limit at which a warning shows. Sent so the UI cannot disagree with the server.\"\n warnAt: Float!\n \"True once any dimension is past warnAt and still admitted.\"\n warning: Boolean!\n \"True once any dimension has reached its limit, so work is being refused.\"\n exceeded: Boolean!\n }\n\n \"\"\"\n The usage report a spend panel renders.\n\n One query rather than several, so a panel cannot show a total from one moment and a breakdown from another.\n \"\"\"\n type UsageReport {\n period: String!\n from: String!\n to: String!\n totals: UsageTotals!\n buckets: [UsageBucket!]!\n byModel: [UsageBreakdownEntry!]!\n byConversation: [UsageBreakdownEntry!]!\n quota: UsageQuota\n currency: String!\n }\n\n \"A single transport event; payload carries the typed part / lifecycle detail.\"\n type RunEvent {\n type: String!\n runId: ID!\n sequence: Int!\n occurredAt: DateTime!\n payload: JSON!\n }\n\n \"\"\"\n One question put to a person, as it must be *rendered* \u2014 #163.\n\n The event that suspends a run carries only an interactionId, deliberately: events are thin, and a payload\n that duplicated the question would be a second copy to keep in step with the stored one. But nothing exposed\n the stored one either, so a client could answer a question it had no way to display. The example's picker\n rendered an empty text box next to \"The assistant has a question\", which is the whole gap in one screenshot.\n \"\"\"\n type PendingQuestionSpec {\n \"Stable key the answer is filed under.\"\n key: String!\n prompt: String!\n \"A short closed list, when there is one. Empty means free text.\"\n options: [String!]!\n \"Several choices are allowed, not one.\"\n multiple: Boolean!\n \"Free text is accepted alongside the options.\"\n allowOther: Boolean!\n }\n\n type PendingQuestion {\n interactionId: ID!\n runId: ID!\n questions: [PendingQuestionSpec!]!\n createdAt: DateTime!\n }\n\n \"\"\"\n The approval a run is parked on \u2014 the read side of decideApproval (#163).\n\n The same gap as pendingQuestion and milder rather than absent: approval.requested also carries only an\n interaction id, so a client had no summary to show and fell back to a generic \"Run a tool?\". Asking someone\n to authorise an action the card cannot name is how approval becomes a reflex.\n \"\"\"\n type PendingApprovalDetail {\n interactionId: ID!\n runId: ID!\n toolName: String!\n \"One line a person can decide on, written by the host's summarizer.\"\n summary: String!\n riskCategory: String!\n expiresAt: DateTime!\n \"The arguments the approval is for, so what runs is what was shown.\"\n normalizedInput: JSON!\n }\n\n input QuestionAnswerInput {\n interactionId: ID!\n runId: ID!\n answers: JSON!\n }\n\n input ApprovalDecisionInput {\n interactionId: ID!\n runId: ID!\n decision: String!\n }\n\n \"A section that shaped a turn's prompt \u2014 for the context inspector (#39).\"\n type InspectedSection {\n title: String!\n providerId: String!\n kind: String!\n provenance: String!\n estimatedTokens: Int!\n sensitivity: String!\n included: Boolean!\n prunedReason: String\n }\n\n type ContextInspection {\n sections: [InspectedSection!]!\n totalTokens: Int!\n budget: JSON!\n }\n\n type Query {\n conversations(limit: Int!, cursor: String): ConversationPage!\n conversation(id: ID!): Conversation\n run(id: ID!): Run\n toolCatalog(preloaded: [String!]!, categories: [String!]!, excluded: [String!]!): ToolCatalog!\n usage(runId: ID): UsageTotals!\n \"\"\"\n Consumption and cost by period, with breakdowns and quota state (#140).\n\n An extension of the usage query rather than a second endpoint: a panel showing a total from one query and a\n breakdown from another can show two moments at once, and the discrepancy looks like a bug in the numbers.\n \"\"\"\n usageReport(period: String!, from: String!, to: String!, breakdownLimit: Int): UsageReport!\n \"What context shaped a turn \u2014 attributes memory/tools/history that influenced the prompt.\"\n conversationContext(conversationId: ID!, runId: ID): ContextInspection\n \"\"\"\n The question a run is parked on, or null \u2014 the read side of answerQuestion (#163).\n\n Null covers both \"this run was never asked anything\" and \"it has been answered already\", because a client\n has the same thing to do in either case: show no picker. A run that is waiting is the only state with a\n question to render.\n \"\"\"\n pendingQuestion(runId: ID!): PendingQuestion\n \"The approval a run is parked on, or null (#163).\"\n pendingApproval(runId: ID!): PendingApprovalDetail\n }\n\n type Mutation {\n createConversation(id: ID!, title: String!): Conversation!\n renameConversation(id: ID!, expectedVersion: Int!, title: String!): Conversation!\n archiveConversation(id: ID!, expectedVersion: Int!): Conversation!\n deleteConversation(id: ID!): Boolean!\n sendMessage(conversationId: ID!, runId: ID!): Run!\n cancelRun(runId: ID!): Boolean!\n answerQuestion(input: QuestionAnswerInput!): Boolean!\n decideApproval(input: ApprovalDecisionInput!): Boolean!\n }\n\n type Subscription {\n \"Conversation/run events, resumable after a cursor (sequence).\"\n runEvents(runId: ID!, conversationId: ID!, after: Int): RunEvent!\n }\n";
|
|
10
|
+
export declare const typeDefs = "\n scalar JSON\n scalar DateTime\n\n type Conversation {\n id: ID!\n title: String!\n version: Int!\n archivedAt: DateTime\n createdAt: DateTime!\n updatedAt: DateTime!\n }\n\n type ConversationPage {\n items: [Conversation!]!\n nextCursor: String\n }\n\n enum RunStatus {\n queued\n running\n waiting_for_question\n waiting_for_approval\n retry_pending\n completed\n failed\n cancelled\n }\n\n type Run {\n id: ID!\n \"Null for a run that belongs to no conversation \u2014 a triggered automation rather than a chat turn (#198).\"\n conversationId: ID\n status: RunStatus!\n createdAt: DateTime!\n finishedAt: DateTime\n }\n\n type ToolCatalogEntry {\n name: String!\n label: String!\n description: String!\n category: String!\n effect: String!\n }\n\n \"\"\"\n What was withheld from a catalogue, and why \u2014 REQ-045 (#204), task #210.\n\n Present only when a budget bound. A client rendering a shortened list with nothing saying it was shortened\n is the same invisible failure the run event exists to prevent, one layer out.\n \"\"\"\n type ToolCatalogTruncation {\n budgetTokens: Int!\n residentTokens: Int!\n dropped: [String!]!\n \"Whether find_tools is wired, which is the difference between a deferral and a removal.\"\n findable: Boolean!\n \"True when the protected set alone exceeds the budget: a misconfiguration, not the mechanism working.\"\n overBudget: Boolean!\n }\n\n \"The tenant's category switches as they were applied \u2014 task #210, AC-4.\"\n type TenantToolset {\n enabledCategories: [String!]\n disabledCategories: [String!]\n }\n\n type ToolCatalog {\n preloaded: [JSON!]!\n discoverable: [ToolCatalogEntry!]!\n meta: [ToolCatalogEntry!]!\n truncation: ToolCatalogTruncation\n toolset: TenantToolset\n }\n\n \"A tool found by describing a need \u2014 task #210, AC-1. Filtered by the same authorization as discovery.\"\n type ToolSearchHit {\n entry: ToolCatalogEntry!\n score: Float!\n signals: [String!]!\n }\n\n type ToolSearchResult {\n hits: [ToolSearchHit!]!\n \"The signals actually used. Keyword-only when no embedding provider is wired.\"\n modes: [String!]!\n }\n\n type UsageTotals {\n inputTokens: Int!\n outputTokens: Int!\n cachedInputTokens: Int!\n costMinorUnits: Int!\n eventCount: Int!\n }\n\n \"One period's consumption. #139's rollups, so a chart never scans raw records.\"\n type UsageBucket {\n bucketStart: String!\n totals: UsageTotals!\n currency: String!\n }\n\n \"Consumption grouped by model or conversation over the requested range.\"\n type UsageBreakdownEntry {\n key: String!\n totals: UsageTotals!\n }\n\n \"\"\"\n Where the tenant stands against its limit.\n\n Absent when no limit is configured \u2014 which means unbounded, not zero. A UI must show \"no limit set\"\n rather than a full bar.\n \"\"\"\n type UsageQuota {\n \"\"\"\n The window in words \u2014 \"the day\", \"any 5 hours\" (#181).\n\n period stayed for the calendar case and is **null** for a rolling window, because no RollupPeriod describes\n one and returning \"hour\" for a five-hour window would be a wrong answer rather than a missing one. A client\n rendering the window reads this field; one keying a chart by bucket reads period and correctly finds nothing\n to key by.\n \"\"\"\n window: String!\n period: String\n \"The model this limit covers, or null for any model (#182).\"\n modelId: String\n costLimitMinorUnits: Int\n inputTokenLimit: Int\n outputTokenLimit: Int\n \"The fraction of the limit at which a warning shows. Sent so the UI cannot disagree with the server.\"\n warnAt: Float!\n \"True once any dimension is past warnAt and still admitted.\"\n warning: Boolean!\n \"True once any dimension has reached its limit, so work is being refused.\"\n exceeded: Boolean!\n }\n\n \"\"\"\n The usage report a spend panel renders.\n\n One query rather than several, so a panel cannot show a total from one moment and a breakdown from another.\n \"\"\"\n type UsageReport {\n period: String!\n from: String!\n to: String!\n totals: UsageTotals!\n buckets: [UsageBucket!]!\n byModel: [UsageBreakdownEntry!]!\n byConversation: [UsageBreakdownEntry!]!\n quota: UsageQuota\n currency: String!\n }\n\n \"A single transport event; payload carries the typed part / lifecycle detail.\"\n type RunEvent {\n type: String!\n runId: ID!\n sequence: Int!\n occurredAt: DateTime!\n payload: JSON!\n }\n\n \"\"\"\n One question put to a person, as it must be *rendered* \u2014 #163.\n\n The event that suspends a run carries only an interactionId, deliberately: events are thin, and a payload\n that duplicated the question would be a second copy to keep in step with the stored one. But nothing exposed\n the stored one either, so a client could answer a question it had no way to display. The example's picker\n rendered an empty text box next to \"The assistant has a question\", which is the whole gap in one screenshot.\n \"\"\"\n type PendingQuestionSpec {\n \"Stable key the answer is filed under.\"\n key: String!\n prompt: String!\n \"A short closed list, when there is one. Empty means free text.\"\n options: [String!]!\n \"Several choices are allowed, not one.\"\n multiple: Boolean!\n \"Free text is accepted alongside the options.\"\n allowOther: Boolean!\n }\n\n type PendingQuestion {\n interactionId: ID!\n runId: ID!\n questions: [PendingQuestionSpec!]!\n createdAt: DateTime!\n }\n\n \"\"\"\n The approval a run is parked on \u2014 the read side of decideApproval (#163).\n\n The same gap as pendingQuestion and milder rather than absent: approval.requested also carries only an\n interaction id, so a client had no summary to show and fell back to a generic \"Run a tool?\". Asking someone\n to authorise an action the card cannot name is how approval becomes a reflex.\n \"\"\"\n type PendingApprovalDetail {\n interactionId: ID!\n runId: ID!\n toolName: String!\n \"One line a person can decide on, written by the host's summarizer.\"\n summary: String!\n riskCategory: String!\n expiresAt: DateTime!\n \"The arguments the approval is for, so what runs is what was shown.\"\n normalizedInput: JSON!\n }\n\n input QuestionAnswerInput {\n interactionId: ID!\n runId: ID!\n answers: JSON!\n }\n\n input ApprovalDecisionInput {\n interactionId: ID!\n runId: ID!\n decision: String!\n }\n\n \"A section that shaped a turn's prompt \u2014 for the context inspector (#39).\"\n type InspectedSection {\n title: String!\n providerId: String!\n kind: String!\n provenance: String!\n estimatedTokens: Int!\n sensitivity: String!\n included: Boolean!\n prunedReason: String\n }\n\n type ContextInspection {\n sections: [InspectedSection!]!\n totalTokens: Int!\n budget: JSON!\n }\n\n type Query {\n conversations(limit: Int!, cursor: String): ConversationPage!\n conversation(id: ID!): Conversation\n run(id: ID!): Run\n toolCatalog(preloaded: [String!]!, categories: [String!]!, excluded: [String!]!): ToolCatalog!\n \"\"\"\n Search the catalogue by describing what you need \u2014 task #210, AC-1.\n\n Returns nothing at all when no search is configured, rather than failing: a client asking for a capability\n the deployment did not wire should render an empty result, not an error dialog.\n \"\"\"\n findTools(query: String!, limit: Int): ToolSearchResult!\n usage(runId: ID): UsageTotals!\n \"\"\"\n Consumption and cost by period, with breakdowns and quota state (#140).\n\n An extension of the usage query rather than a second endpoint: a panel showing a total from one query and a\n breakdown from another can show two moments at once, and the discrepancy looks like a bug in the numbers.\n \"\"\"\n usageReport(period: String!, from: String!, to: String!, breakdownLimit: Int): UsageReport!\n \"What context shaped a turn \u2014 attributes memory/tools/history that influenced the prompt.\"\n conversationContext(conversationId: ID!, runId: ID): ContextInspection\n \"\"\"\n The question a run is parked on, or null \u2014 the read side of answerQuestion (#163).\n\n Null covers both \"this run was never asked anything\" and \"it has been answered already\", because a client\n has the same thing to do in either case: show no picker. A run that is waiting is the only state with a\n question to render.\n \"\"\"\n pendingQuestion(runId: ID!): PendingQuestion\n \"The approval a run is parked on, or null (#163).\"\n pendingApproval(runId: ID!): PendingApprovalDetail\n }\n\n type Mutation {\n createConversation(id: ID!, title: String!): Conversation!\n renameConversation(id: ID!, expectedVersion: Int!, title: String!): Conversation!\n archiveConversation(id: ID!, expectedVersion: Int!): Conversation!\n deleteConversation(id: ID!): Boolean!\n sendMessage(conversationId: ID!, runId: ID!): Run!\n cancelRun(runId: ID!): Boolean!\n answerQuestion(input: QuestionAnswerInput!): Boolean!\n decideApproval(input: ApprovalDecisionInput!): Boolean!\n }\n\n type Subscription {\n \"Conversation/run events, resumable after a cursor (sequence).\"\n runEvents(runId: ID!, conversationId: ID!, after: Int): RunEvent!\n }\n";
|
|
11
11
|
//# sourceMappingURL=schema.d.ts.map
|
package/dist/graphql/schema.js
CHANGED
|
@@ -53,10 +53,47 @@ export const typeDefs = /* GraphQL */ `
|
|
|
53
53
|
effect: String!
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
"""
|
|
57
|
+
What was withheld from a catalogue, and why — REQ-045 (#204), task #210.
|
|
58
|
+
|
|
59
|
+
Present only when a budget bound. A client rendering a shortened list with nothing saying it was shortened
|
|
60
|
+
is the same invisible failure the run event exists to prevent, one layer out.
|
|
61
|
+
"""
|
|
62
|
+
type ToolCatalogTruncation {
|
|
63
|
+
budgetTokens: Int!
|
|
64
|
+
residentTokens: Int!
|
|
65
|
+
dropped: [String!]!
|
|
66
|
+
"Whether find_tools is wired, which is the difference between a deferral and a removal."
|
|
67
|
+
findable: Boolean!
|
|
68
|
+
"True when the protected set alone exceeds the budget: a misconfiguration, not the mechanism working."
|
|
69
|
+
overBudget: Boolean!
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
"The tenant's category switches as they were applied — task #210, AC-4."
|
|
73
|
+
type TenantToolset {
|
|
74
|
+
enabledCategories: [String!]
|
|
75
|
+
disabledCategories: [String!]
|
|
76
|
+
}
|
|
77
|
+
|
|
56
78
|
type ToolCatalog {
|
|
57
79
|
preloaded: [JSON!]!
|
|
58
80
|
discoverable: [ToolCatalogEntry!]!
|
|
59
81
|
meta: [ToolCatalogEntry!]!
|
|
82
|
+
truncation: ToolCatalogTruncation
|
|
83
|
+
toolset: TenantToolset
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
"A tool found by describing a need — task #210, AC-1. Filtered by the same authorization as discovery."
|
|
87
|
+
type ToolSearchHit {
|
|
88
|
+
entry: ToolCatalogEntry!
|
|
89
|
+
score: Float!
|
|
90
|
+
signals: [String!]!
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type ToolSearchResult {
|
|
94
|
+
hits: [ToolSearchHit!]!
|
|
95
|
+
"The signals actually used. Keyword-only when no embedding provider is wired."
|
|
96
|
+
modes: [String!]!
|
|
60
97
|
}
|
|
61
98
|
|
|
62
99
|
type UsageTotals {
|
|
@@ -217,6 +254,13 @@ export const typeDefs = /* GraphQL */ `
|
|
|
217
254
|
conversation(id: ID!): Conversation
|
|
218
255
|
run(id: ID!): Run
|
|
219
256
|
toolCatalog(preloaded: [String!]!, categories: [String!]!, excluded: [String!]!): ToolCatalog!
|
|
257
|
+
"""
|
|
258
|
+
Search the catalogue by describing what you need — task #210, AC-1.
|
|
259
|
+
|
|
260
|
+
Returns nothing at all when no search is configured, rather than failing: a client asking for a capability
|
|
261
|
+
the deployment did not wire should render an empty result, not an error dialog.
|
|
262
|
+
"""
|
|
263
|
+
findTools(query: String!, limit: Int): ToolSearchResult!
|
|
220
264
|
usage(runId: ID): UsageTotals!
|
|
221
265
|
"""
|
|
222
266
|
Consumption and cost by period, with breakdowns and quota state (#140).
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardrails — REQ-046 (#205), task #211.
|
|
3
|
+
*
|
|
4
|
+
* A seam for checks a deployment needs and this runtime does not ship: PII redaction, moderation, a topic
|
|
5
|
+
* restriction, an output schema. Without it, a deployment that needs any of those has to edit the engine.
|
|
6
|
+
*
|
|
7
|
+
* The injection half of "guardrails" is already built and lives elsewhere (`security/prompt-safety.ts`), and it
|
|
8
|
+
* is deliberately *not* a guardrail in this sense: containment is structural — untrusted content is wrapped in a
|
|
9
|
+
* nonce-delimited envelope whether or not anything recognises an attack — whereas everything here is
|
|
10
|
+
* *inspection*, which can only act on what it detects. Conflating the two would invite someone to switch off
|
|
11
|
+
* containment because a detector is present.
|
|
12
|
+
*
|
|
13
|
+
* ## Three decisions that make this worth having
|
|
14
|
+
*
|
|
15
|
+
* **Tool calls are outputs.** A guardrail that inspects only the final message can be walked straight past by
|
|
16
|
+
* putting the data in a tool argument. Checking prose and not arguments is checking the boring half, so
|
|
17
|
+
* `GuardrailOutput` is a discriminated union of a message *and* a tool call, and the tool-call case is enforced
|
|
18
|
+
* at the one choke point every call goes through.
|
|
19
|
+
*
|
|
20
|
+
* **Fail closed.** A guardrail that throws refuses the turn, attributed to the guardrail that threw. The
|
|
21
|
+
* opposite default is how a guardrail silently stops guarding the day its dependency times out — and the run
|
|
22
|
+
* looks entirely normal afterwards, which is the property that makes it dangerous.
|
|
23
|
+
*
|
|
24
|
+
* **Every verdict is recorded, and never the value.** A redaction that leaves no trace is indistinguishable from
|
|
25
|
+
* the model never having been told, which makes an incident unreconstructable. So a record names *what* was
|
|
26
|
+
* redacted — the field, the entity type — and never what it contained, or the audit trail becomes the leak.
|
|
27
|
+
*/
|
|
28
|
+
import type { ExecutionContext } from "../core/context.js";
|
|
29
|
+
/** What a guardrail may conclude. */
|
|
30
|
+
export declare const GUARDRAIL_OUTCOMES: readonly ["pass", "redacted", "refused"];
|
|
31
|
+
export type GuardrailOutcome = (typeof GUARDRAIL_OUTCOMES)[number];
|
|
32
|
+
/** The turn's input, before the model sees it. */
|
|
33
|
+
export type GuardrailInput = {
|
|
34
|
+
readonly text: string;
|
|
35
|
+
/** Identifiers only. A guardrail that needs contents reads them through the file service, under its own budget. */
|
|
36
|
+
readonly attachmentIds?: readonly string[];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Every boundary crossing that is not the turn's own input.
|
|
40
|
+
*
|
|
41
|
+
* Three kinds, one hook, deliberately. A separate hook per kind lets an author implement two of three and have a
|
|
42
|
+
* gap they did not choose — and the gap would be in whichever kind was added last, which is the one nobody
|
|
43
|
+
* remembers. The union forces the `switch` to be written.
|
|
44
|
+
*
|
|
45
|
+
* `tool-result` is here even though a tool produced it rather than the model: it is content *entering* the
|
|
46
|
+
* model's context from outside the tenant, and it is the likeliest source of personal data in a whole run — a
|
|
47
|
+
* document read by a tool contains what the document contains. Inspecting the arguments and not the results
|
|
48
|
+
* would check the direction data leaves and ignore the direction it arrives.
|
|
49
|
+
*/
|
|
50
|
+
export type GuardrailOutput = {
|
|
51
|
+
readonly kind: "message";
|
|
52
|
+
readonly text: string;
|
|
53
|
+
} | {
|
|
54
|
+
readonly kind: "tool-call";
|
|
55
|
+
readonly toolName: string;
|
|
56
|
+
readonly input: unknown;
|
|
57
|
+
} | {
|
|
58
|
+
readonly kind: "tool-result";
|
|
59
|
+
readonly toolName: string;
|
|
60
|
+
readonly output: unknown;
|
|
61
|
+
};
|
|
62
|
+
export type GuardrailVerdict<T> = {
|
|
63
|
+
readonly kind: "pass";
|
|
64
|
+
}
|
|
65
|
+
/** `what` names fields or entity types — never values. */
|
|
66
|
+
| {
|
|
67
|
+
readonly kind: "redacted";
|
|
68
|
+
readonly value: T;
|
|
69
|
+
readonly what: readonly string[];
|
|
70
|
+
} | {
|
|
71
|
+
readonly kind: "refused";
|
|
72
|
+
readonly code: string;
|
|
73
|
+
readonly message: string;
|
|
74
|
+
};
|
|
75
|
+
export interface Guardrail {
|
|
76
|
+
/** Stable, and used in records and refusals: "which check stopped this" must be answerable. */
|
|
77
|
+
readonly name: string;
|
|
78
|
+
inspectInput?(input: GuardrailInput, context: ExecutionContext): Promise<GuardrailVerdict<GuardrailInput>> | GuardrailVerdict<GuardrailInput>;
|
|
79
|
+
inspectOutput?(output: GuardrailOutput, context: ExecutionContext): Promise<GuardrailVerdict<GuardrailOutput>> | GuardrailVerdict<GuardrailOutput>;
|
|
80
|
+
}
|
|
81
|
+
/** One line of the audit trail. Carries no inspected value, by construction. */
|
|
82
|
+
export type GuardrailRecord = {
|
|
83
|
+
readonly guardrail: string;
|
|
84
|
+
readonly subject: "input" | "message" | "tool-call" | "tool-result";
|
|
85
|
+
readonly outcome: GuardrailOutcome;
|
|
86
|
+
/** For a redaction: the fields or entity types touched. Never their contents. */
|
|
87
|
+
readonly what?: readonly string[];
|
|
88
|
+
/** For a refusal. */
|
|
89
|
+
readonly code?: string;
|
|
90
|
+
/** True when the guardrail threw and was therefore treated as a refusal. */
|
|
91
|
+
readonly threw?: boolean;
|
|
92
|
+
};
|
|
93
|
+
export type GuardrailDecision<T> = {
|
|
94
|
+
readonly outcome: "allowed";
|
|
95
|
+
readonly value: T;
|
|
96
|
+
readonly records: readonly GuardrailRecord[];
|
|
97
|
+
} | {
|
|
98
|
+
readonly outcome: "refused";
|
|
99
|
+
readonly by: string;
|
|
100
|
+
readonly code: string;
|
|
101
|
+
readonly message: string;
|
|
102
|
+
readonly records: readonly GuardrailRecord[];
|
|
103
|
+
};
|
|
104
|
+
/** Before the model sees the turn. */
|
|
105
|
+
export declare const applyInputGuardrails: (guardrails: readonly Guardrail[], input: GuardrailInput, context: ExecutionContext) => Promise<GuardrailDecision<GuardrailInput>>;
|
|
106
|
+
/** Before anything leaves the model — a message *or* a tool call. */
|
|
107
|
+
export declare const applyOutputGuardrails: (guardrails: readonly Guardrail[], output: GuardrailOutput, context: ExecutionContext) => Promise<GuardrailDecision<GuardrailOutput>>;
|
|
108
|
+
/**
|
|
109
|
+
* Whether a record could carry an inspected value — used by the test that asserts it never does.
|
|
110
|
+
*
|
|
111
|
+
* Here rather than in the test file because it states the invariant next to the type it constrains: a record has
|
|
112
|
+
* a fixed shape, and adding a field that holds content is the change this is meant to make somebody notice.
|
|
113
|
+
*/
|
|
114
|
+
export declare const recordCarriesOnlyMetadata: (record: GuardrailRecord) => boolean;
|
|
115
|
+
//# sourceMappingURL=index.d.ts.map
|