@retinue/agentkit 0.1.0 → 0.3.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/audio/openai.d.ts +36 -0
- package/dist/adapters/audio/openai.js +136 -0
- package/dist/adapters/bullmq/lock.d.ts +26 -2
- package/dist/adapters/embeddings/openai.d.ts +45 -0
- package/dist/adapters/embeddings/openai.js +109 -0
- package/dist/adapters/memory/connections.d.ts +10 -0
- package/dist/adapters/memory/connections.js +87 -0
- package/dist/adapters/memory/graph.d.ts +17 -0
- package/dist/adapters/memory/graph.js +267 -0
- package/dist/adapters/memory/index.d.ts +3 -0
- package/dist/adapters/memory/index.js +3 -0
- package/dist/adapters/memory/rate-limit.d.ts +14 -0
- package/dist/adapters/memory/rate-limit.js +28 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/postgres/connections.d.ts +15 -0
- package/dist/adapters/postgres/connections.js +134 -0
- package/dist/adapters/postgres/graph.d.ts +24 -0
- package/dist/adapters/postgres/graph.js +325 -0
- package/dist/adapters/postgres/index.d.ts +2 -0
- package/dist/adapters/postgres/index.js +2 -0
- package/dist/adapters/postgres/migrations.js +198 -0
- package/dist/adapters/postgres/run-store.js +26 -2
- package/dist/adapters/postgres/schema.d.ts +61 -2
- package/dist/adapters/postgres/schema.js +95 -10
- package/dist/adapters/redis/index.d.ts +1 -0
- package/dist/adapters/redis/index.js +1 -0
- package/dist/adapters/redis/rate-limit.d.ts +46 -0
- package/dist/adapters/redis/rate-limit.js +55 -0
- package/dist/adapters/supabase/index.d.ts +15 -0
- package/dist/adapters/supabase/index.js +15 -0
- package/dist/adapters/supabase/rls.js +33 -0
- package/dist/agents/agent.d.ts +99 -1
- package/dist/agents/agent.js +215 -16
- package/dist/agents/define.js +24 -4
- package/dist/agents/engine.d.ts +48 -0
- package/dist/agents/engine.js +370 -13
- package/dist/audio/index.d.ts +151 -0
- package/dist/audio/index.js +183 -0
- 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/connections/cipher.d.ts +103 -0
- package/dist/connections/cipher.js +141 -0
- package/dist/connections/index.d.ts +156 -0
- package/dist/connections/index.js +72 -0
- package/dist/connections/oauth/client.d.ts +84 -0
- package/dist/connections/oauth/client.js +141 -0
- package/dist/connections/oauth/index.d.ts +166 -0
- package/dist/connections/oauth/index.js +226 -0
- package/dist/connections/oauth/service.d.ts +90 -0
- package/dist/connections/oauth/service.js +132 -0
- package/dist/connections/pause.d.ts +68 -0
- package/dist/connections/pause.js +97 -0
- package/dist/connections/resolver.d.ts +59 -0
- package/dist/connections/resolver.js +138 -0
- package/dist/connections/resume.d.ts +64 -0
- package/dist/connections/resume.js +99 -0
- package/dist/connections/vault-cipher.d.ts +95 -0
- package/dist/connections/vault-cipher.js +206 -0
- package/dist/core/budget.d.ts +55 -0
- package/dist/core/budget.js +56 -0
- package/dist/core/content-parts.d.ts +26 -2
- package/dist/core/content-parts.js +1 -0
- package/dist/core/context.d.ts +25 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.js +15 -0
- package/dist/core/events.d.ts +110 -2
- package/dist/core/events.js +33 -2
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/validation.js +19 -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/connections.d.ts +10 -0
- package/dist/entries/connections.js +10 -0
- package/dist/entries/guardrails.d.ts +14 -0
- package/dist/entries/guardrails.js +14 -0
- package/dist/entries/knowledge.d.ts +17 -0
- package/dist/entries/knowledge.js +16 -0
- package/dist/entries/mcp-server.d.ts +9 -0
- package/dist/entries/mcp-server.js +9 -0
- package/dist/entries/runtime.d.ts +10 -0
- package/dist/entries/runtime.js +8 -0
- package/dist/entries/testing.d.ts +28 -0
- package/dist/entries/testing.js +28 -0
- package/dist/graphql/resolvers.d.ts +7 -1
- package/dist/graphql/resolvers.js +20 -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/communities.d.ts +166 -0
- package/dist/knowledge/communities.js +377 -0
- package/dist/knowledge/graph-global.d.ts +153 -0
- package/dist/knowledge/graph-global.js +246 -0
- package/dist/knowledge/graph-retrieval.d.ts +150 -0
- package/dist/knowledge/graph-retrieval.js +303 -0
- package/dist/knowledge/graph.d.ts +223 -0
- package/dist/knowledge/graph.js +369 -0
- package/dist/knowledge/index.d.ts +25 -0
- package/dist/knowledge/index.js +37 -1
- package/dist/knowledge/navigate.d.ts +89 -0
- package/dist/knowledge/navigate.js +107 -0
- package/dist/knowledge/retrieval.d.ts +98 -5
- package/dist/knowledge/retrieval.js +158 -28
- package/dist/mcp-server/index.d.ts +168 -0
- package/dist/mcp-server/index.js +175 -0
- package/dist/models/extraction.d.ts +73 -0
- package/dist/models/extraction.js +118 -0
- package/dist/models/index.d.ts +40 -1
- package/dist/models/index.js +22 -1
- package/dist/models/pricing.d.ts +2 -0
- package/dist/models/pricing.js +17 -1
- package/dist/models/provider-factory.js +9 -7
- package/dist/models/streaming.d.ts +111 -1
- package/dist/models/streaming.js +184 -3
- package/dist/persistence/index.d.ts +230 -0
- package/dist/persistence/index.js +2 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +12 -0
- package/dist/runtime/worker.d.ts +36 -1
- package/dist/runtime/worker.js +37 -4
- package/dist/security/checklist.js +55 -0
- package/dist/security/findings.js +18 -9
- package/dist/server/bin.d.ts +19 -0
- package/dist/server/bin.js +226 -0
- package/dist/server/boot.d.ts +20 -2
- package/dist/server/boot.js +29 -3
- package/dist/server/cli.d.ts +13 -0
- package/dist/server/cli.js +7 -4
- package/dist/server/doctor.d.ts +74 -0
- package/dist/server/doctor.js +280 -0
- package/dist/skills/catalogue.d.ts +49 -0
- package/dist/skills/catalogue.js +61 -0
- package/dist/skills/context.d.ts +61 -0
- package/dist/skills/context.js +107 -0
- package/dist/skills/index.d.ts +2 -0
- package/dist/skills/index.js +2 -0
- package/dist/telemetry/spans.js +17 -0
- package/dist/testing/conformance/artifact-exports.d.ts +20 -0
- package/dist/testing/conformance/artifact-exports.js +172 -0
- package/dist/testing/conformance/artifacts.d.ts +11 -0
- package/dist/testing/conformance/artifacts.js +316 -0
- package/dist/testing/conformance/audio.d.ts +38 -0
- package/dist/testing/conformance/audio.js +145 -0
- package/dist/testing/conformance/capability.d.ts +23 -0
- package/dist/testing/conformance/capability.js +27 -0
- package/dist/testing/conformance/checkpoint-store.d.ts +15 -0
- package/dist/testing/conformance/checkpoint-store.js +72 -0
- package/dist/testing/conformance/connections.d.ts +14 -0
- package/dist/testing/conformance/connections.js +171 -0
- package/dist/testing/conformance/conversation-store.d.ts +10 -0
- package/dist/testing/conformance/conversation-store.js +60 -0
- package/dist/testing/conformance/evaluation.d.ts +10 -0
- package/dist/testing/conformance/evaluation.js +295 -0
- package/dist/testing/conformance/files.d.ts +19 -0
- package/dist/testing/conformance/files.js +454 -0
- package/dist/testing/conformance/flows.d.ts +16 -0
- package/dist/testing/conformance/flows.js +193 -0
- package/dist/testing/conformance/graph.d.ts +22 -0
- package/dist/testing/conformance/graph.js +500 -0
- package/dist/testing/conformance/hitl.d.ts +25 -0
- package/dist/testing/conformance/hitl.js +523 -0
- package/dist/testing/conformance/index.d.ts +163 -0
- package/dist/testing/conformance/index.js +321 -0
- package/dist/testing/conformance/invariants.d.ts +23 -0
- package/dist/testing/conformance/invariants.js +80 -0
- package/dist/testing/conformance/knowledge.d.ts +41 -0
- package/dist/testing/conformance/knowledge.js +592 -0
- package/dist/testing/conformance/parents.d.ts +50 -0
- package/dist/testing/conformance/parents.js +39 -0
- package/dist/testing/conformance/rate-limit.d.ts +25 -0
- package/dist/testing/conformance/rate-limit.js +71 -0
- package/dist/testing/conformance/records.d.ts +36 -0
- package/dist/testing/conformance/records.js +400 -0
- package/dist/testing/conformance/rollups.d.ts +30 -0
- package/dist/testing/conformance/rollups.js +425 -0
- package/dist/testing/conformance/run-coordinator.d.ts +27 -0
- package/dist/testing/conformance/run-coordinator.js +150 -0
- package/dist/testing/conformance/run-event-log.d.ts +9 -0
- package/dist/testing/conformance/run-event-log.js +138 -0
- package/dist/testing/conformance/run-store.d.ts +12 -0
- package/dist/testing/conformance/run-store.js +294 -0
- package/dist/testing/conformance/session-state.d.ts +38 -0
- package/dist/testing/conformance/session-state.js +198 -0
- package/dist/testing/conformance/usage-limits.d.ts +10 -0
- package/dist/testing/conformance/usage-limits.js +309 -0
- package/dist/testing/conformance.d.ts +8 -0
- package/dist/testing/conformance.js +8 -0
- package/dist/testing/memory-backend.d.ts +39 -0
- package/dist/testing/memory-backend.js +41 -0
- package/dist/testing/pglite.d.ts +55 -0
- package/dist/testing/pglite.js +78 -0
- package/dist/testing/stub-model.d.ts +58 -0
- package/dist/testing/stub-model.js +71 -0
- package/dist/testing/supabase-storage-double.d.ts +35 -0
- package/dist/testing/supabase-storage-double.js +101 -0
- package/dist/toolkit/files.d.ts +125 -0
- package/dist/toolkit/files.js +320 -0
- package/dist/toolkit/http.d.ts +27 -0
- package/dist/toolkit/http.js +48 -0
- package/dist/toolkit/index.d.ts +31 -1
- package/dist/toolkit/index.js +26 -1
- package/dist/toolkit/mime.d.ts +97 -0
- package/dist/toolkit/mime.js +233 -0
- package/dist/toolkit/sandbox.d.ts +119 -0
- package/dist/toolkit/sandbox.js +239 -0
- package/dist/toolkit/ssrf.d.ts +139 -0
- package/dist/toolkit/ssrf.js +349 -0
- package/dist/toolkit/vendor.d.ts +91 -0
- package/dist/toolkit/vendor.js +153 -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 +316 -0
- package/dist/tools/credentials.js +369 -0
- package/dist/tools/define.d.ts +45 -0
- package/dist/tools/define.js +26 -0
- package/dist/tools/find.d.ts +109 -0
- package/dist/tools/find.js +210 -0
- package/dist/tools/index.d.ts +35 -2
- package/dist/tools/index.js +11 -0
- package/dist/tools/library/audio.d.ts +63 -0
- package/dist/tools/library/audio.js +148 -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 +37 -2
- package/dist/tools/library/index.js +47 -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 +146 -0
- package/dist/tools/registry.js +250 -4
- package/dist/usage/index.d.ts +21 -0
- package/dist/usage/index.js +1 -0
- package/dist/usage/rate-limit.d.ts +122 -0
- package/dist/usage/rate-limit.js +131 -0
- package/dist/usage/recorder.js +3 -0
- package/package.json +33 -2
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hearing and speaking — REQ-062 (#257), task #258, Part 2.
|
|
3
|
+
*
|
|
4
|
+
* Two ports, because the two acts are genuinely separate and a deployment will often want one and not the
|
|
5
|
+
* other: transcription turns audio into text so a text-only model can read it, and speech turns text into
|
|
6
|
+
* audio so an interface can play it.
|
|
7
|
+
*
|
|
8
|
+
* ## Why ports rather than tools that call an API
|
|
9
|
+
*
|
|
10
|
+
* The same reason `tools-search` supplies providers instead of tools. Whisper, Deepgram, AssemblyAI and a
|
|
11
|
+
* self-hosted `whisper.cpp` are four *values*, not four sets of tools — and which one a deployment uses is a
|
|
12
|
+
* decision about cost, residency and latency that a model should never spend a tool call discovering.
|
|
13
|
+
*
|
|
14
|
+
* It also keeps AC-2's real case honest: *"so a deployment can use a model that does not accept audio
|
|
15
|
+
* natively"*. Most models do not. Transcribing first and sending text is how audio reaches them at all, and
|
|
16
|
+
* that path has to exist independently of any one provider.
|
|
17
|
+
*
|
|
18
|
+
* ## Bounds are enforced before the upload, not after
|
|
19
|
+
*
|
|
20
|
+
* AC-4 says so and it is the part most easily got wrong: the natural implementation reads the file, stores it,
|
|
21
|
+
* and then checks. A rejected 200MB recording must not be stored first — it costs the blob write, the
|
|
22
|
+
* retention obligation and the deletion, all for something that was never going to be accepted.
|
|
23
|
+
*
|
|
24
|
+
* So the check takes a *declared* size and media type and answers before any bytes move. `assertWithinBounds`
|
|
25
|
+
* is the function a caller runs at the boundary; nothing here reads a file to find out whether it may.
|
|
26
|
+
*
|
|
27
|
+
* ## Duration is bounded too, and it cannot be checked from the size
|
|
28
|
+
*
|
|
29
|
+
* A byte ceiling is not a duration ceiling: an hour of 8kbps speech is smaller than a minute of uncompressed
|
|
30
|
+
* WAV. Both matter for different reasons — bytes bound the storage and the upload, seconds bound the *cost*,
|
|
31
|
+
* because audio is billed per second. A deployment that bounded only bytes would have an unbounded bill.
|
|
32
|
+
*
|
|
33
|
+
* Duration is therefore checked where it becomes known: declared by the caller if it knows, and reported by
|
|
34
|
+
* the provider afterwards. `assertWithinBounds` refuses a declared duration over the ceiling before the call;
|
|
35
|
+
* `TranscriptionResult.durationSeconds` is what usage is charged on.
|
|
36
|
+
*/
|
|
37
|
+
import { type PlatformError } from "../core/errors.js";
|
|
38
|
+
/**
|
|
39
|
+
* The media types accepted, and it is a list rather than `audio/*` on purpose.
|
|
40
|
+
*
|
|
41
|
+
* A provider rejects an unknown container with a message about the request, not about the format, so the
|
|
42
|
+
* useful refusal happens here. Every entry is one that the shipped adapters actually accept.
|
|
43
|
+
*/
|
|
44
|
+
export declare const AUDIO_MEDIA_TYPES: readonly ["audio/mpeg", "audio/mp4", "audio/wav", "audio/webm", "audio/ogg", "audio/flac", "audio/x-m4a"];
|
|
45
|
+
export type AudioMediaType = (typeof AUDIO_MEDIA_TYPES)[number];
|
|
46
|
+
export declare const isAudioMediaType: (mediaType: string) => mediaType is AudioMediaType;
|
|
47
|
+
/**
|
|
48
|
+
* 25MB, which is where every hosted transcription API this could target draws its own line.
|
|
49
|
+
*
|
|
50
|
+
* Matching the provider's limit rather than inventing a smaller one: a deployment that wants less sets its
|
|
51
|
+
* own, and a ceiling *above* the provider's would just move the failure later and make it theirs to explain.
|
|
52
|
+
*/
|
|
53
|
+
export declare const MAX_AUDIO_BYTES: number;
|
|
54
|
+
/**
|
|
55
|
+
* One hour.
|
|
56
|
+
*
|
|
57
|
+
* The number is about cost rather than capability. At a typical per-second rate an hour is already a
|
|
58
|
+
* noticeable charge for a single tool call, and a run that transcribes a six-hour recording by accident is the
|
|
59
|
+
* kind of bill nobody notices until the invoice.
|
|
60
|
+
*/
|
|
61
|
+
export declare const MAX_AUDIO_SECONDS = 3600;
|
|
62
|
+
/**
|
|
63
|
+
* The transcript ceiling — AC-5.
|
|
64
|
+
*
|
|
65
|
+
* An hour of speech is roughly 9,000 words, which fits. This bounds the *pathological* case: a provider that
|
|
66
|
+
* returns a repeated hallucination on silence, which is a known Whisper failure mode and produces megabytes of
|
|
67
|
+
* one phrase. Truncation is reported, never silent — a shortened transcript that claimed to be complete would
|
|
68
|
+
* make the model summarise a fragment as if it were the whole recording.
|
|
69
|
+
*/
|
|
70
|
+
export declare const MAX_TRANSCRIPT_CHARS = 100000;
|
|
71
|
+
/** What speech generation may be asked to say. Bounds the cost the same way the audio ceiling does. */
|
|
72
|
+
export declare const MAX_SPEECH_CHARS = 4000;
|
|
73
|
+
export type AudioBounds = {
|
|
74
|
+
readonly maxBytes?: number;
|
|
75
|
+
readonly maxSeconds?: number;
|
|
76
|
+
};
|
|
77
|
+
export declare class AudioRejected extends Error {
|
|
78
|
+
readonly code: "invalid_input";
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Refuses before anything is stored or sent — AC-4.
|
|
82
|
+
*
|
|
83
|
+
* Takes what a caller knows *without reading the file*: the declared byte length and media type from the
|
|
84
|
+
* upload, and a duration if the client measured one. Deliberately not given the bytes: a function that
|
|
85
|
+
* received them would invite reading them, and the whole point is to answer before they move.
|
|
86
|
+
*/
|
|
87
|
+
export declare const assertWithinBounds: (input: {
|
|
88
|
+
readonly byteSize: number;
|
|
89
|
+
readonly mediaType: string;
|
|
90
|
+
readonly durationSeconds?: number;
|
|
91
|
+
}, bounds?: AudioBounds) => void;
|
|
92
|
+
/** A transcript, bounded, with the truncation reported rather than hidden. */
|
|
93
|
+
export type TranscriptionResult = {
|
|
94
|
+
readonly text: string;
|
|
95
|
+
/**
|
|
96
|
+
* What usage is charged on — AC-8. Reported by the provider, because only it knows.
|
|
97
|
+
*
|
|
98
|
+
* A provider that does not report it leaves this absent rather than guessing, and the recorder then charges
|
|
99
|
+
* nothing for the audio. That under-bills, which is why the shipped adapter asks for a response format that
|
|
100
|
+
* includes it.
|
|
101
|
+
*/
|
|
102
|
+
readonly durationSeconds?: number;
|
|
103
|
+
readonly truncated: boolean;
|
|
104
|
+
/** The provider's own language detection, when it offers one. Not a translation — see the REQ's scope. */
|
|
105
|
+
readonly language?: string;
|
|
106
|
+
};
|
|
107
|
+
export type TranscriptionRequest = {
|
|
108
|
+
readonly audio: Uint8Array;
|
|
109
|
+
readonly mediaType: string;
|
|
110
|
+
readonly filename?: string;
|
|
111
|
+
/** A hint, not an instruction. A provider free to detect is usually better at it than a caller guessing. */
|
|
112
|
+
readonly languageHint?: string;
|
|
113
|
+
readonly maxTranscriptChars?: number;
|
|
114
|
+
};
|
|
115
|
+
export interface TranscriptionProvider {
|
|
116
|
+
readonly id: string;
|
|
117
|
+
transcribe(request: TranscriptionRequest): Promise<TranscriptionResult>;
|
|
118
|
+
}
|
|
119
|
+
export type SpeechRequest = {
|
|
120
|
+
readonly text: string;
|
|
121
|
+
/** The provider's own voice identifier. Opaque here: naming a fixed set would date immediately. */
|
|
122
|
+
readonly voice?: string;
|
|
123
|
+
readonly format?: "mp3" | "wav" | "opus" | "flac";
|
|
124
|
+
};
|
|
125
|
+
export type SpeechResult = {
|
|
126
|
+
readonly audio: Uint8Array;
|
|
127
|
+
readonly mediaType: string;
|
|
128
|
+
/** Absent when the provider does not report it. Charged on when present. */
|
|
129
|
+
readonly durationSeconds?: number;
|
|
130
|
+
};
|
|
131
|
+
export interface SpeechProvider {
|
|
132
|
+
readonly id: string;
|
|
133
|
+
speak(request: SpeechRequest): Promise<SpeechResult>;
|
|
134
|
+
}
|
|
135
|
+
/** Bounds a transcript and says whether it had to. */
|
|
136
|
+
export declare const boundTranscript: (text: string, maxChars?: number) => {
|
|
137
|
+
text: string;
|
|
138
|
+
truncated: boolean;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* A provider failure, in the platform's vocabulary — AC-5.
|
|
142
|
+
*
|
|
143
|
+
* Deliberately the same shape and the same distinctions as `describeFetchFailure`: 429 and 5xx retryable,
|
|
144
|
+
* unreachable `provider_unavailable`, everything else terminal. A second vocabulary for audio would mean the
|
|
145
|
+
* runtime's retry logic treated a transcription rate limit differently from a scrape rate limit for no reason
|
|
146
|
+
* anybody chose.
|
|
147
|
+
*/
|
|
148
|
+
export declare const describeAudioFailure: (error: unknown, provider: string) => PlatformError;
|
|
149
|
+
/** Throws the classified failure, for a caller that would rather not branch. */
|
|
150
|
+
export declare const failAudio: (error: unknown, provider: string) => never;
|
|
151
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hearing and speaking — REQ-062 (#257), task #258, Part 2.
|
|
3
|
+
*
|
|
4
|
+
* Two ports, because the two acts are genuinely separate and a deployment will often want one and not the
|
|
5
|
+
* other: transcription turns audio into text so a text-only model can read it, and speech turns text into
|
|
6
|
+
* audio so an interface can play it.
|
|
7
|
+
*
|
|
8
|
+
* ## Why ports rather than tools that call an API
|
|
9
|
+
*
|
|
10
|
+
* The same reason `tools-search` supplies providers instead of tools. Whisper, Deepgram, AssemblyAI and a
|
|
11
|
+
* self-hosted `whisper.cpp` are four *values*, not four sets of tools — and which one a deployment uses is a
|
|
12
|
+
* decision about cost, residency and latency that a model should never spend a tool call discovering.
|
|
13
|
+
*
|
|
14
|
+
* It also keeps AC-2's real case honest: *"so a deployment can use a model that does not accept audio
|
|
15
|
+
* natively"*. Most models do not. Transcribing first and sending text is how audio reaches them at all, and
|
|
16
|
+
* that path has to exist independently of any one provider.
|
|
17
|
+
*
|
|
18
|
+
* ## Bounds are enforced before the upload, not after
|
|
19
|
+
*
|
|
20
|
+
* AC-4 says so and it is the part most easily got wrong: the natural implementation reads the file, stores it,
|
|
21
|
+
* and then checks. A rejected 200MB recording must not be stored first — it costs the blob write, the
|
|
22
|
+
* retention obligation and the deletion, all for something that was never going to be accepted.
|
|
23
|
+
*
|
|
24
|
+
* So the check takes a *declared* size and media type and answers before any bytes move. `assertWithinBounds`
|
|
25
|
+
* is the function a caller runs at the boundary; nothing here reads a file to find out whether it may.
|
|
26
|
+
*
|
|
27
|
+
* ## Duration is bounded too, and it cannot be checked from the size
|
|
28
|
+
*
|
|
29
|
+
* A byte ceiling is not a duration ceiling: an hour of 8kbps speech is smaller than a minute of uncompressed
|
|
30
|
+
* WAV. Both matter for different reasons — bytes bound the storage and the upload, seconds bound the *cost*,
|
|
31
|
+
* because audio is billed per second. A deployment that bounded only bytes would have an unbounded bill.
|
|
32
|
+
*
|
|
33
|
+
* Duration is therefore checked where it becomes known: declared by the caller if it knows, and reported by
|
|
34
|
+
* the provider afterwards. `assertWithinBounds` refuses a declared duration over the ceiling before the call;
|
|
35
|
+
* `TranscriptionResult.durationSeconds` is what usage is charged on.
|
|
36
|
+
*/
|
|
37
|
+
import { AgentPlatformError } from "../core/errors.js";
|
|
38
|
+
/**
|
|
39
|
+
* The media types accepted, and it is a list rather than `audio/*` on purpose.
|
|
40
|
+
*
|
|
41
|
+
* A provider rejects an unknown container with a message about the request, not about the format, so the
|
|
42
|
+
* useful refusal happens here. Every entry is one that the shipped adapters actually accept.
|
|
43
|
+
*/
|
|
44
|
+
export const AUDIO_MEDIA_TYPES = [
|
|
45
|
+
"audio/mpeg",
|
|
46
|
+
"audio/mp4",
|
|
47
|
+
"audio/wav",
|
|
48
|
+
"audio/webm",
|
|
49
|
+
"audio/ogg",
|
|
50
|
+
"audio/flac",
|
|
51
|
+
"audio/x-m4a",
|
|
52
|
+
];
|
|
53
|
+
export const isAudioMediaType = (mediaType) => AUDIO_MEDIA_TYPES.includes(mediaType.split(";")[0]?.trim().toLowerCase() ?? "");
|
|
54
|
+
/**
|
|
55
|
+
* 25MB, which is where every hosted transcription API this could target draws its own line.
|
|
56
|
+
*
|
|
57
|
+
* Matching the provider's limit rather than inventing a smaller one: a deployment that wants less sets its
|
|
58
|
+
* own, and a ceiling *above* the provider's would just move the failure later and make it theirs to explain.
|
|
59
|
+
*/
|
|
60
|
+
export const MAX_AUDIO_BYTES = 25 * 1024 * 1024;
|
|
61
|
+
/**
|
|
62
|
+
* One hour.
|
|
63
|
+
*
|
|
64
|
+
* The number is about cost rather than capability. At a typical per-second rate an hour is already a
|
|
65
|
+
* noticeable charge for a single tool call, and a run that transcribes a six-hour recording by accident is the
|
|
66
|
+
* kind of bill nobody notices until the invoice.
|
|
67
|
+
*/
|
|
68
|
+
export const MAX_AUDIO_SECONDS = 3_600;
|
|
69
|
+
/**
|
|
70
|
+
* The transcript ceiling — AC-5.
|
|
71
|
+
*
|
|
72
|
+
* An hour of speech is roughly 9,000 words, which fits. This bounds the *pathological* case: a provider that
|
|
73
|
+
* returns a repeated hallucination on silence, which is a known Whisper failure mode and produces megabytes of
|
|
74
|
+
* one phrase. Truncation is reported, never silent — a shortened transcript that claimed to be complete would
|
|
75
|
+
* make the model summarise a fragment as if it were the whole recording.
|
|
76
|
+
*/
|
|
77
|
+
export const MAX_TRANSCRIPT_CHARS = 100_000;
|
|
78
|
+
/** What speech generation may be asked to say. Bounds the cost the same way the audio ceiling does. */
|
|
79
|
+
export const MAX_SPEECH_CHARS = 4_000;
|
|
80
|
+
export class AudioRejected extends Error {
|
|
81
|
+
code = "invalid_input";
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Refuses before anything is stored or sent — AC-4.
|
|
85
|
+
*
|
|
86
|
+
* Takes what a caller knows *without reading the file*: the declared byte length and media type from the
|
|
87
|
+
* upload, and a duration if the client measured one. Deliberately not given the bytes: a function that
|
|
88
|
+
* received them would invite reading them, and the whole point is to answer before they move.
|
|
89
|
+
*/
|
|
90
|
+
export const assertWithinBounds = (input, bounds = {}) => {
|
|
91
|
+
const maxBytes = bounds.maxBytes ?? MAX_AUDIO_BYTES;
|
|
92
|
+
const maxSeconds = bounds.maxSeconds ?? MAX_AUDIO_SECONDS;
|
|
93
|
+
if (!isAudioMediaType(input.mediaType)) {
|
|
94
|
+
throw new AudioRejected(`${input.mediaType} is not an audio format this deployment accepts. Accepted: ` +
|
|
95
|
+
`${AUDIO_MEDIA_TYPES.join(", ")}.`);
|
|
96
|
+
}
|
|
97
|
+
if (!Number.isFinite(input.byteSize) || input.byteSize <= 0) {
|
|
98
|
+
throw new AudioRejected("An audio attachment needs a declared size, and it must be greater than zero.");
|
|
99
|
+
}
|
|
100
|
+
if (input.byteSize > maxBytes) {
|
|
101
|
+
throw new AudioRejected(`That recording is ${Math.round(input.byteSize / 1024 / 1024)}MB and the limit is ` +
|
|
102
|
+
`${Math.round(maxBytes / 1024 / 1024)}MB. Refused before upload — nothing was stored.`);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Duration only when the caller declared one.
|
|
106
|
+
*
|
|
107
|
+
* Absent is not an error: a browser knows the duration of a recording it just made and a server receiving a
|
|
108
|
+
* file often does not, and demanding it would mean either refusing legitimate uploads or reading the file to
|
|
109
|
+
* find out — which is the thing this function exists to avoid.
|
|
110
|
+
*/
|
|
111
|
+
if (input.durationSeconds !== undefined && input.durationSeconds > maxSeconds) {
|
|
112
|
+
throw new AudioRejected(`That recording is ${Math.round(input.durationSeconds / 60)} minutes and the limit is ` +
|
|
113
|
+
`${Math.round(maxSeconds / 60)}. Refused before upload — nothing was stored.`);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/** Bounds a transcript and says whether it had to. */
|
|
117
|
+
export const boundTranscript = (text, maxChars = MAX_TRANSCRIPT_CHARS) => {
|
|
118
|
+
if (text.length <= maxChars)
|
|
119
|
+
return { text, truncated: false };
|
|
120
|
+
/**
|
|
121
|
+
* Cut at a word boundary when there is one nearby.
|
|
122
|
+
*
|
|
123
|
+
* A transcript sliced mid-word reads as a transcription error rather than as a truncation, and a model
|
|
124
|
+
* summarising it will occasionally treat the fragment as a real word. The 200-character window is small
|
|
125
|
+
* enough that the ceiling still means what it says.
|
|
126
|
+
*/
|
|
127
|
+
const hard = text.slice(0, maxChars);
|
|
128
|
+
const lastSpace = hard.lastIndexOf(" ");
|
|
129
|
+
/**
|
|
130
|
+
* `lastSpace > 0` matters, and its absence was a bug the tests caught.
|
|
131
|
+
*
|
|
132
|
+
* `lastIndexOf` returns `-1` when there is no space at all — and `-1 > maxChars - 200` is *true* for any
|
|
133
|
+
* ceiling under 200, so the original condition sliced to `-1` and silently dropped the final character of
|
|
134
|
+
* every space-free transcript. A one-character loss that no assertion about truncation would notice.
|
|
135
|
+
*/
|
|
136
|
+
const useBoundary = lastSpace > 0 && lastSpace > maxChars - 200;
|
|
137
|
+
return { text: useBoundary ? hard.slice(0, lastSpace) : hard, truncated: true };
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* A provider failure, in the platform's vocabulary — AC-5.
|
|
141
|
+
*
|
|
142
|
+
* Deliberately the same shape and the same distinctions as `describeFetchFailure`: 429 and 5xx retryable,
|
|
143
|
+
* unreachable `provider_unavailable`, everything else terminal. A second vocabulary for audio would mean the
|
|
144
|
+
* runtime's retry logic treated a transcription rate limit differently from a scrape rate limit for no reason
|
|
145
|
+
* anybody chose.
|
|
146
|
+
*/
|
|
147
|
+
export const describeAudioFailure = (error, provider) => {
|
|
148
|
+
if (error instanceof AudioRejected) {
|
|
149
|
+
return { code: "invalid_input", message: error.message, retryable: false };
|
|
150
|
+
}
|
|
151
|
+
const status = error.status;
|
|
152
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
153
|
+
if (status === 429) {
|
|
154
|
+
return { code: "rate_limited", message: `${provider} is rate limiting: ${message}`, retryable: true };
|
|
155
|
+
}
|
|
156
|
+
if (status === 413) {
|
|
157
|
+
// Not retryable, and worth its own arm: the file is too large and will be next time too.
|
|
158
|
+
return {
|
|
159
|
+
code: "invalid_input",
|
|
160
|
+
message: `${provider} refused the recording as too large: ${message}`,
|
|
161
|
+
retryable: false,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (status === 401 || status === 403) {
|
|
165
|
+
return {
|
|
166
|
+
code: "unauthorized",
|
|
167
|
+
message: `${provider} refused the credential: ${message}. Retrying will not help.`,
|
|
168
|
+
retryable: false,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (status !== undefined && status >= 500) {
|
|
172
|
+
return { code: "provider_unavailable", message: `${provider} returned ${status}: ${message}`, retryable: true };
|
|
173
|
+
}
|
|
174
|
+
if (/timed out|etimedout|abort|fetch failed|econnrefused|enotfound/i.test(message)) {
|
|
175
|
+
return { code: "provider_unavailable", message: `${provider} could not be reached: ${message}`, retryable: true };
|
|
176
|
+
}
|
|
177
|
+
return { code: "provider_error", message: `${provider} could not process the audio: ${message}`, retryable: false };
|
|
178
|
+
};
|
|
179
|
+
/** Throws the classified failure, for a caller that would rather not branch. */
|
|
180
|
+
export const failAudio = (error, provider) => {
|
|
181
|
+
throw new AgentPlatformError(describeAudioFailure(error, provider));
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* which auto-approves a stated set of effects, and is auditable afterwards where a flag leaves no record. The
|
|
35
35
|
* difference is between "nobody had to approve this" and "we cannot tell whether anyone should have".
|
|
36
36
|
*/
|
|
37
|
-
export declare const CAPABILITIES: readonly ["history", "memory", "compaction", "citations", "questions", "skills", "mcp", "usage"];
|
|
37
|
+
export declare const CAPABILITIES: readonly ["history", "memory", "compaction", "citations", "questions", "skills", "mcp", "usage", "guardrails", "shell"];
|
|
38
38
|
export type Capability = (typeof CAPABILITIES)[number];
|
|
39
39
|
export type CapabilityState = "on" | "off";
|
|
40
40
|
export type CapabilityMap = Readonly<Record<Capability, CapabilityState>>;
|
|
@@ -62,6 +62,8 @@ export declare const PROFILES: {
|
|
|
62
62
|
readonly skills: "on";
|
|
63
63
|
readonly mcp: "off";
|
|
64
64
|
readonly usage: "on";
|
|
65
|
+
readonly guardrails: "off";
|
|
66
|
+
readonly shell: "off";
|
|
65
67
|
};
|
|
66
68
|
/**
|
|
67
69
|
* A headless automation: no conversation, no person, no recall.
|
|
@@ -78,6 +80,8 @@ export declare const PROFILES: {
|
|
|
78
80
|
readonly skills: "off";
|
|
79
81
|
readonly mcp: "off";
|
|
80
82
|
readonly usage: "on";
|
|
83
|
+
readonly guardrails: "off";
|
|
84
|
+
readonly shell: "off";
|
|
81
85
|
};
|
|
82
86
|
};
|
|
83
87
|
export type ProfileName = keyof typeof PROFILES;
|
|
@@ -44,6 +44,8 @@ export const CAPABILITIES = [
|
|
|
44
44
|
"skills",
|
|
45
45
|
"mcp",
|
|
46
46
|
"usage",
|
|
47
|
+
"guardrails",
|
|
48
|
+
"shell",
|
|
47
49
|
];
|
|
48
50
|
/**
|
|
49
51
|
* What each capability cannot work without, by the name a host wires it under.
|
|
@@ -62,6 +64,18 @@ export const CAPABILITY_REQUIRES = {
|
|
|
62
64
|
skills: ["skills"],
|
|
63
65
|
mcp: ["mcpConnections", "mcpClient"],
|
|
64
66
|
usage: ["usage"],
|
|
67
|
+
// A guardrail set, supplied by the host. Declaring the capability without wiring one is refused at
|
|
68
|
+
// construction — which is the point: "guardrails: on" must mean a check exists, not that somebody intended one.
|
|
69
|
+
guardrails: ["guardrails"],
|
|
70
|
+
/**
|
|
71
|
+
* A sandbox for `shell_exec` — REQ-047 (#206), task #215.
|
|
72
|
+
*
|
|
73
|
+
* The only capability whose *absence* is a security property rather than a missing feature. `shell_exec` is
|
|
74
|
+
* arbitrary code execution with a natural-language trigger, so it takes two switches: a sandbox wired, and this
|
|
75
|
+
* declared. Declaring it with no sandbox is refused at construction, and wiring a sandbox without declaring it
|
|
76
|
+
* leaves the tool present and refusing — which is the safe direction of the two.
|
|
77
|
+
*/
|
|
78
|
+
shell: ["sandbox"],
|
|
65
79
|
};
|
|
66
80
|
const OFF = Object.freeze(Object.fromEntries(CAPABILITIES.map((c) => [c, "off"])));
|
|
67
81
|
/**
|
|
@@ -81,6 +95,13 @@ export const PROFILES = {
|
|
|
81
95
|
skills: "on",
|
|
82
96
|
mcp: "off",
|
|
83
97
|
usage: "on",
|
|
98
|
+
// Off in both profiles, deliberately. A guardrail set is the host's — and a profile that turned this on
|
|
99
|
+
// would be a profile that refuses to construct until somebody supplies one, which is a poor default for a
|
|
100
|
+
// named starting point.
|
|
101
|
+
guardrails: "off",
|
|
102
|
+
// Off in both profiles, and this one should never be otherwise: no named starting point gets to decide that
|
|
103
|
+
// an application can run shell commands.
|
|
104
|
+
shell: "off",
|
|
84
105
|
},
|
|
85
106
|
/**
|
|
86
107
|
* A headless automation: no conversation, no person, no recall.
|
|
@@ -97,6 +118,8 @@ export const PROFILES = {
|
|
|
97
118
|
skills: "off",
|
|
98
119
|
mcp: "off",
|
|
99
120
|
usage: "on",
|
|
121
|
+
guardrails: "off",
|
|
122
|
+
shell: "off",
|
|
100
123
|
},
|
|
101
124
|
};
|
|
102
125
|
/** A profile as a plain map, so a caller can read a default before adopting it. */
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* capability. Not a function a caller must remember to call first: the ones who forget are exactly the six
|
|
19
19
|
* defects this module exists for. Every consumer of a store goes through the same property.
|
|
20
20
|
*/
|
|
21
|
+
import type { Guardrail } from "../guardrails/index.js";
|
|
21
22
|
import type { RunEventLog } from "../core/events.js";
|
|
22
23
|
import type { InteractionStore, MessageStore, RunStore, SkillStore, ThreadSummaryStore, UsageStore } from "../persistence/index.js";
|
|
23
24
|
import type { PrincipalMemoryStore } from "../principal-memory/index.js";
|
|
@@ -56,6 +57,13 @@ export type RuntimeStores = {
|
|
|
56
57
|
export type RuntimeServices = {
|
|
57
58
|
readonly summarizer?: ThreadSummarizer;
|
|
58
59
|
readonly mcpClient?: McpClient;
|
|
60
|
+
/**
|
|
61
|
+
* Ordered, and the order is the host's — REQ-046 (#205), AC-7.
|
|
62
|
+
*
|
|
63
|
+
* An array rather than a record, because composition order decides the outcome when two guardrails both
|
|
64
|
+
* redact, and a record's key order is an implementation detail that changes when somebody reformats a config.
|
|
65
|
+
*/
|
|
66
|
+
readonly guardrails?: readonly Guardrail[];
|
|
59
67
|
};
|
|
60
68
|
export type CreateRuntimeInput = {
|
|
61
69
|
readonly profile?: ProfileName;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SecretCipher` — encryption at rest for third-party credentials, REQ-063 (#259), task #261.
|
|
3
|
+
*
|
|
4
|
+
* `docs/21`'s Connections section sets the requirement, and it is precise enough to decide the design:
|
|
5
|
+
*
|
|
6
|
+
* > encryption at rest with **a key the application database cannot decrypt on its own**
|
|
7
|
+
*
|
|
8
|
+
* That sentence rules out the two obvious implementations.
|
|
9
|
+
*
|
|
10
|
+
* ## Why not `pgcrypto`
|
|
11
|
+
*
|
|
12
|
+
* `pgp_sym_encrypt(data, key)` needs the key passed into each query. A key stored in the same database as the
|
|
13
|
+
* ciphertext satisfies nothing — it protects against a stolen backup and nothing else, and the sentence above
|
|
14
|
+
* rules it out by name. A key passed as a SQL literal is worse: it can land in `pg_stat_statements`, in the
|
|
15
|
+
* server log and in a slow-query log, which are three places nobody thinks of as credential stores.
|
|
16
|
+
*
|
|
17
|
+
* `pgsodium` and its transparent column encryption are deprecated; do not build on them.
|
|
18
|
+
*
|
|
19
|
+
* ## Why not Supabase Vault alone
|
|
20
|
+
*
|
|
21
|
+
* Supabase Vault is genuinely good — libsodium, and a root key held outside the database, so it satisfies the
|
|
22
|
+
* requirement. It is also available only on Supabase. Building on it would leave the Postgres and in-memory
|
|
23
|
+
* adapters storing plaintext, so the conformance suite would be asserting *different guarantees per adapter* —
|
|
24
|
+
* which is the one thing a conformance suite must never do.
|
|
25
|
+
*
|
|
26
|
+
* ## So: encrypt in the application, behind a seam
|
|
27
|
+
*
|
|
28
|
+
* The store holds an opaque ciphertext blob and knows nothing about how it was produced. Every adapter family
|
|
29
|
+
* gets the identical property, no key passes through SQL, and Vault becomes one *implementation* of this seam
|
|
30
|
+
* rather than the foundation — a choice a deployment makes, not a dependency it inherits.
|
|
31
|
+
*
|
|
32
|
+
* The honest trade-off, which belongs in the docs rather than in a footnote: app-side encryption means the
|
|
33
|
+
* application process holds the key in memory. Vault means the database performs the decryption and therefore
|
|
34
|
+
* sees plaintext. Neither is strictly better, and a deployment should pick knowing that.
|
|
35
|
+
*
|
|
36
|
+
* **The Vault implementation shipped in #268**, in `vault-cipher.ts`, and every clause of this file's test
|
|
37
|
+
* suite was run against a real Vault before it landed — which is why it was held back until one was available.
|
|
38
|
+
* Two properties differ from app-side sealing and are argued there rather than here: a Vault-sealed row holds
|
|
39
|
+
* a **pointer, not ciphertext**, which changes what a backup contains; and a pointer cannot be authenticated
|
|
40
|
+
* by what it points at, so integrity is provided by a binding token rather than by AEAD.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* A stored secret: ciphertext plus what is needed to read it back.
|
|
44
|
+
*
|
|
45
|
+
* `keyId` is the reason rotation is possible at all — see `createAesGcmCipher`. Everything here is safe to
|
|
46
|
+
* store beside the ciphertext; none of it is secret on its own.
|
|
47
|
+
*/
|
|
48
|
+
export type SealedSecret = {
|
|
49
|
+
/** Which key sealed this. A design with no key identity has a first key that is permanent. */
|
|
50
|
+
readonly keyId: string;
|
|
51
|
+
/** Algorithm, so a future one can be added without guessing what old rows used. */
|
|
52
|
+
readonly algorithm: string;
|
|
53
|
+
/** Base64. */
|
|
54
|
+
readonly nonce: string;
|
|
55
|
+
/** Base64, including the authentication tag. */
|
|
56
|
+
readonly ciphertext: string;
|
|
57
|
+
};
|
|
58
|
+
export interface SecretCipher {
|
|
59
|
+
seal(plaintext: string): Promise<SealedSecret>;
|
|
60
|
+
open(sealed: SealedSecret): Promise<string>;
|
|
61
|
+
/** The key new secrets are sealed with. Exposed so a rotation job can tell what still needs re-sealing. */
|
|
62
|
+
currentKeyId(): string;
|
|
63
|
+
/**
|
|
64
|
+
* Destroy whatever the sealed value refers to — **optional, and found by #268.**
|
|
65
|
+
*
|
|
66
|
+
* App-side sealing has nothing to implement: the ciphertext *is* the stored value, so deleting the row
|
|
67
|
+
* deletes the secret. A cipher that stores a **pointer** — Supabase Vault returns an id and keeps the
|
|
68
|
+
* ciphertext itself — does not, and dropping a connection row there leaves the credential behind, which is
|
|
69
|
+
* the opposite of what `docs/18` promises.
|
|
70
|
+
*
|
|
71
|
+
* Optional rather than required because most implementations genuinely have nothing to do, and a required
|
|
72
|
+
* no-op is a method every future cipher has to write in order to say "not applicable". A caller deleting a
|
|
73
|
+
* connection should call it when present.
|
|
74
|
+
*/
|
|
75
|
+
forget?(sealed: SealedSecret): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
export declare const AES_GCM: "aes-256-gcm";
|
|
78
|
+
export type AesKey = {
|
|
79
|
+
readonly id: string;
|
|
80
|
+
/** 32 bytes. A shorter key is a configuration error, not a weaker mode. */
|
|
81
|
+
readonly key: Buffer;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* AES-256-GCM, with a key set rather than a key.
|
|
85
|
+
*
|
|
86
|
+
* **Rotation is why this takes a list.** A design with one key has a first key that is permanent: re-encrypting
|
|
87
|
+
* every stored secret requires reading them, which requires the old key, which the process no longer has. So
|
|
88
|
+
* every sealed secret records its `keyId`, `open` looks the key up, and rotation is: add a new key, make it
|
|
89
|
+
* current, re-seal in the background, retire the old one. A deployment that never rotates passes one key and
|
|
90
|
+
* notices nothing.
|
|
91
|
+
*
|
|
92
|
+
* **Authenticated encryption, and the authentication is the point.** GCM's tag means a tampered ciphertext
|
|
93
|
+
* *fails* rather than decrypting to something else. Without it, a byte flipped in a database row would produce
|
|
94
|
+
* garbage that gets sent to a vendor as a token — a failure that looks like an expired credential and is not.
|
|
95
|
+
*/
|
|
96
|
+
export declare const createAesGcmCipher: (input: {
|
|
97
|
+
readonly keys: readonly AesKey[];
|
|
98
|
+
readonly currentKeyId?: string;
|
|
99
|
+
readonly randomNonce?: () => Buffer;
|
|
100
|
+
}) => SecretCipher;
|
|
101
|
+
/** Constant-time comparison, for callers checking a sealed secret against another without leaking timing. */
|
|
102
|
+
export declare const sealedEquals: (a: SealedSecret, b: SealedSecret) => boolean;
|
|
103
|
+
//# sourceMappingURL=cipher.d.ts.map
|