fix: honor audio_as_voice streaming + parse tests (#490) (thanks @jarvis-medmatic)

This commit is contained in:
Peter Steinberger
2026-01-10 01:50:33 +01:00
parent 5fedfd8d15
commit c56b2f4bc1
7 changed files with 32 additions and 4 deletions

View File

@@ -776,6 +776,7 @@ export async function compactEmbeddedPiSession(params: {
const enqueueGlobal =
params.enqueue ??
((task, opts) => enqueueCommandInLane(globalLane, task, opts));
const runAbortController = new AbortController();
return enqueueCommandInLane(sessionLane, () =>
enqueueGlobal(async () => {
const resolvedWorkspace = resolveUserPath(params.workspaceDir);

View File

@@ -651,7 +651,9 @@ export function createClawdbotCodingTools(options?: {
// Without this, some providers (notably OpenAI) will reject root-level union schemas.
const normalized = subagentFiltered.map(normalizeToolParameters);
const withAbort = options?.abortSignal
? normalized.map((tool) => wrapToolWithAbortSignal(tool, options.abortSignal))
? normalized.map((tool) =>
wrapToolWithAbortSignal(tool, options.abortSignal),
)
: normalized;
// Anthropic blocks specific lowercase tool names (bash, read, write, edit) with OAuth tokens.

View File

@@ -3,6 +3,7 @@ import type { ClawdbotConfig } from "../../config/config.js";
import {
loadSessionStore,
resolveStorePath,
type SessionEntry,
saveSessionStore,
} from "../../config/sessions.js";
import {
@@ -35,7 +36,7 @@ export function setAbortMemory(key: string, value: boolean): void {
}
function resolveSessionEntryForKey(
store: Record<string, { sessionId: string; updatedAt: number }> | undefined,
store: Record<string, SessionEntry> | undefined,
sessionKey: string | undefined,
) {
if (!store || !sessionKey) return {};

View File

@@ -7,7 +7,10 @@ import type { ReplyDispatcher } from "./reply-dispatcher.js";
const mocks = vi.hoisted(() => ({
routeReply: vi.fn(async () => ({ ok: true, messageId: "mock" })),
tryFastAbortFromMessage: vi.fn(async () => ({ handled: false, aborted: false })),
tryFastAbortFromMessage: vi.fn(async () => ({
handled: false,
aborted: false,
})),
}));
vi.mock("./route-reply.js", () => ({

19
src/media/parse.test.ts Normal file
View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { splitMediaFromOutput } from "./parse.js";
describe("splitMediaFromOutput", () => {
it("detects audio_as_voice tag and strips it", () => {
const result = splitMediaFromOutput("Hello [[audio_as_voice]] world");
expect(result.audioAsVoice).toBe(true);
expect(result.text).toBe("Hello world");
});
it("keeps audio_as_voice detection stable across calls", () => {
const input = "Hello [[audio_as_voice]]";
const first = splitMediaFromOutput(input);
const second = splitMediaFromOutput(input);
expect(first.audioAsVoice).toBe(true);
expect(second.audioAsVoice).toBe(true);
});
});

View File

@@ -34,6 +34,7 @@ function isInsideFence(
// Regex to detect [[audio_as_voice]] tag
const AUDIO_AS_VOICE_RE = /\[\[audio_as_voice\]\]/gi;
const AUDIO_AS_VOICE_TEST_RE = /\[\[audio_as_voice\]\]/i;
export function splitMediaFromOutput(raw: string): {
text: string;
@@ -123,7 +124,7 @@ export function splitMediaFromOutput(raw: string): {
.trim();
// Detect and strip [[audio_as_voice]] tag
const hasAudioAsVoice = AUDIO_AS_VOICE_RE.test(cleanedText);
const hasAudioAsVoice = AUDIO_AS_VOICE_TEST_RE.test(cleanedText);
if (hasAudioAsVoice) {
cleanedText = cleanedText
.replace(AUDIO_AS_VOICE_RE, "")