feat: add guarded image generation controls
This commit is contained in:
@@ -24,6 +24,82 @@ export function eventMatchesRequest(event, requestId) {
|
||||
return !routingId || routingId === requestId;
|
||||
}
|
||||
|
||||
function messageContentItems(event) {
|
||||
const candidates = [
|
||||
event?.message?.content,
|
||||
event?.data?.message?.content,
|
||||
event?.content,
|
||||
];
|
||||
return candidates.find((content) => Array.isArray(content)) ?? [];
|
||||
}
|
||||
|
||||
function parseGenerateImageResult(toolResult) {
|
||||
const content = toolResult?.value?.content;
|
||||
if (!Array.isArray(content)) return null;
|
||||
for (const item of content) {
|
||||
if (item?.type !== 'text' || !String(item.text ?? '').trim()) continue;
|
||||
try {
|
||||
const result = JSON.parse(String(item.text));
|
||||
const mimeType = String(result?.source?.mimeType ?? result?.asset?.mimeType ?? '').toLowerCase();
|
||||
if (
|
||||
result?.ok === true
|
||||
&& String(result?.jobId ?? '').trim()
|
||||
&& /^image\/(?:png|jpeg|webp)$/.test(mimeType)
|
||||
) {
|
||||
return {
|
||||
jobId: String(result.jobId),
|
||||
mimeType,
|
||||
assetId: String(result?.asset?.id ?? '').trim() || null,
|
||||
publicUrl: String(result?.asset?.publicUrl ?? '').trim() || null,
|
||||
workspaceRelativePath: String(result?.asset?.workspaceRelativePath ?? '').trim() || null,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// Ignore non-JSON tool text; it cannot prove a successful image_make delivery.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function createToolEvidenceCollector() {
|
||||
const requestNames = new Map();
|
||||
const calls = new Set();
|
||||
const successfulCalls = new Set();
|
||||
let generateImage = { called: false, succeeded: false };
|
||||
|
||||
return {
|
||||
observe(event) {
|
||||
for (const item of messageContentItems(event)) {
|
||||
if (item?.type === 'toolRequest') {
|
||||
const name = String(item?.toolCall?.value?.name ?? '').trim();
|
||||
if (!name) continue;
|
||||
calls.add(name);
|
||||
if (item.id) requestNames.set(String(item.id), name);
|
||||
if (name.endsWith('generate_image')) generateImage = { called: true, succeeded: false };
|
||||
continue;
|
||||
}
|
||||
if (item?.type !== 'toolResponse') continue;
|
||||
const name = requestNames.get(String(item.id ?? '')) ?? '';
|
||||
const result = item.toolResult ?? {};
|
||||
const succeeded = result.status === 'success' && result?.value?.isError !== true;
|
||||
if (name && succeeded) successfulCalls.add(name);
|
||||
if (!name.endsWith('generate_image')) continue;
|
||||
const generated = succeeded ? parseGenerateImageResult(result) : null;
|
||||
generateImage = generated
|
||||
? { called: true, succeeded: true, ...generated }
|
||||
: { called: true, succeeded: false };
|
||||
}
|
||||
},
|
||||
snapshot() {
|
||||
return {
|
||||
calls: [...calls],
|
||||
successfulCalls: [...successfulCalls],
|
||||
generateImage,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function consumeSessionEventsUntilFinish(
|
||||
body,
|
||||
{
|
||||
@@ -40,6 +116,7 @@ export async function consumeSessionEventsUntilFinish(
|
||||
|
||||
const reader = Readable.fromWeb(body);
|
||||
const decoder = new TextDecoder();
|
||||
const toolEvidence = createToolEvidenceCollector();
|
||||
let buffer = '';
|
||||
const deadline = Date.now() + Math.max(1, Number(timeoutMs) || 1);
|
||||
|
||||
@@ -60,6 +137,7 @@ export async function consumeSessionEventsUntilFinish(
|
||||
const event = parseSessionStreamEvent(trimmed);
|
||||
if (!event) continue;
|
||||
if (!eventMatchesRequest(event, requestId)) continue;
|
||||
toolEvidence.observe(event);
|
||||
onEvent?.(event);
|
||||
if (event.type === 'Error') {
|
||||
const err = new Error(String(event.error ?? 'session reply failed'));
|
||||
@@ -71,6 +149,7 @@ export async function consumeSessionEventsUntilFinish(
|
||||
return {
|
||||
finishEvent: event,
|
||||
tokenState: event.token_state ?? null,
|
||||
toolEvidence: toolEvidence.snapshot(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user