fix(media): disclose the transcription server before dictation uses it
Dictation falls back to the owner's transcription server when the voice provider cannot transcribe, so that server is a voice recipient too: it is listed for voice use (as "Transcription (<origin>)") whenever it would take the recording, and not when the voice provider transcribes itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
This commit is contained in:
parent
97c7fd3a34
commit
ebac3ef9d1
@ -157,6 +157,27 @@ describe("consent grants", () => {
|
||||
expect(JSON.stringify(status)).not.toContain("comfy-key");
|
||||
expect((await aiConsentStatus(deps, actor, { uses: ["voice"] })).recipients).toHaveLength(1);
|
||||
});
|
||||
it("discloses the transcription server for dictation when the voice provider cannot transcribe", async () => {
|
||||
const { deps, actor } = setup();
|
||||
vi.mocked(deps.prisma.integrationProviderConfig.findUnique).mockResolvedValue({
|
||||
id: "media",
|
||||
ciphertext: "fake-media-ciphertext-voice",
|
||||
} as never);
|
||||
deps.secrets = {
|
||||
load: vi.fn(() =>
|
||||
JSON.stringify({ comfyui: null, transcription: { baseUrl: "http://127.0.0.1:8080/v1" } }),
|
||||
),
|
||||
} as unknown as RouterDeps["secrets"];
|
||||
vi.mocked(deps.prisma.spaceVoicePreference.findFirst).mockResolvedValue({
|
||||
credential: { provider: "kokoro" },
|
||||
voiceId: "af_heart",
|
||||
} as never);
|
||||
const status = await aiConsentStatus(deps, actor, { uses: ["voice"] });
|
||||
expect(status.recipients.map((recipient) => [recipient.name, recipient.use])).toEqual([
|
||||
["Local voice", "voice"],
|
||||
["Transcription (http://127.0.0.1:8080)", "voice"],
|
||||
]);
|
||||
});
|
||||
it("returns an operator policy without requiring a hosted provider", async () => {
|
||||
const { deps, actor } = setup();
|
||||
deps.env.privacyPolicyUrl = "https://example.com/privacy";
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
parseModelSecret,
|
||||
selectConfiguredModel,
|
||||
toStringRecord,
|
||||
voiceCatalogEntry,
|
||||
} from "@rakazo/adapters";
|
||||
import type { Actor, AiConsentQuery, AiConsentStatus, AiRecipient } from "@rakazo/contracts";
|
||||
import { AI_DATA_DISCLOSURES, AI_DISCLOSURE_VERSION, AI_PRIVACY_URL } from "@rakazo/contracts";
|
||||
@ -186,6 +187,21 @@ export async function aiConsentStatus(
|
||||
}
|
||||
for (const voice of voices)
|
||||
add(aiRecipient({ provider: voice.credential.provider, use: "voice" }));
|
||||
if (uses.includes("voice")) {
|
||||
// Dictation goes to the owner's transcription server when the voice provider cannot transcribe.
|
||||
const transcription = (await loadMediaSettings(deps)).transcription;
|
||||
const credential = transcription ? await findDefaultVoiceCredential(deps.prisma, actor) : null;
|
||||
if (transcription && !(credential && voiceCatalogEntry(credential.provider)?.transcribe)) {
|
||||
add(
|
||||
aiRecipient({
|
||||
provider: MEDIA_TRANSCRIPTION_PROVIDER_ID,
|
||||
use: "voice",
|
||||
baseUrl: transcription.baseUrl,
|
||||
modelId: transcription.model,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (includesMemory) {
|
||||
if (memoryOwner) {
|
||||
const memory = await deps.prisma.spaceMemoryConfig.findUnique({
|
||||
|
||||
@ -52,6 +52,8 @@ export function aiRecipient(input: {
|
||||
}
|
||||
|
||||
function recipientName(provider: string, use: AiDataUse) {
|
||||
// The transcription server also takes dictation, so it keeps its name for voice use.
|
||||
if (provider === MEDIA_TRANSCRIPTION_PROVIDER_ID) return "Transcription";
|
||||
if (use === "voice") return voiceCatalogEntry(provider)?.name ?? provider;
|
||||
if (use === "memory") {
|
||||
if (provider === "supermemory") return "Supermemory";
|
||||
@ -61,6 +63,5 @@ function recipientName(provider: string, use: AiDataUse) {
|
||||
}
|
||||
if (provider === "cursor") return "Cursor";
|
||||
if (provider === MEDIA_COMFYUI_PROVIDER_ID) return "ComfyUI";
|
||||
if (provider === MEDIA_TRANSCRIPTION_PROVIDER_ID) return "Transcription";
|
||||
return listPiCatalog().find((entry) => entry.provider === provider)?.providerName ?? provider;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user