Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
160 lines
4.4 KiB
TypeScript
160 lines
4.4 KiB
TypeScript
export interface ParsedAssistantContent {
|
|
raw: string;
|
|
display: string;
|
|
reasoning?: string;
|
|
}
|
|
|
|
const REASONING_TYPES = new Set(['reasoning', 'thinking', 'chain_of_thought', 'analysis']);
|
|
|
|
const stripThinkingTags = (input: string) => {
|
|
const reasoningSegments: string[] = [];
|
|
let sanitized = input;
|
|
|
|
const regex = /<think>([\s\S]*?)<\/think>/gi;
|
|
sanitized = sanitized.replace(regex, (_match, inner) => {
|
|
const text = typeof inner === 'string' ? inner.trim() : '';
|
|
if (text) {
|
|
reasoningSegments.push(text);
|
|
}
|
|
return '';
|
|
});
|
|
|
|
return {
|
|
display: sanitized.trim(),
|
|
reasoning:
|
|
reasoningSegments.length > 0 ? reasoningSegments.join('\n\n').trim() : undefined,
|
|
};
|
|
};
|
|
|
|
const serializeContent = (value: unknown, forceReasoning = false): string => {
|
|
if (value == null) {
|
|
return '';
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
return forceReasoning ? `<think>${value}</think>` : value;
|
|
}
|
|
|
|
if (Array.isArray(value)) {
|
|
return value.map((item) => serializeContent(item, forceReasoning)).join('');
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
const part = value as Record<string, unknown>;
|
|
const type = typeof part.type === 'string' ? (part.type as string).toLowerCase() : '';
|
|
const isReasoningType = forceReasoning || REASONING_TYPES.has(type);
|
|
|
|
if ('text' in part && typeof part.text === 'string') {
|
|
return isReasoningType ? `<think>${part.text}</think>` : (part.text as string);
|
|
}
|
|
|
|
if ('value' in part && typeof part.value === 'string') {
|
|
return isReasoningType ? `<think>${part.value}</think>` : (part.value as string);
|
|
}
|
|
|
|
if ('reasoning' in part) {
|
|
const nested = serializeContent(part.reasoning, true);
|
|
if (nested) {
|
|
return nested;
|
|
}
|
|
}
|
|
|
|
if ('content' in part) {
|
|
const nested = serializeContent(part.content, isReasoningType);
|
|
if (nested) {
|
|
return nested;
|
|
}
|
|
}
|
|
|
|
if ('message' in part) {
|
|
const nested = serializeContent(part.message, isReasoningType);
|
|
if (nested) {
|
|
return nested;
|
|
}
|
|
}
|
|
|
|
if ('arguments' in part && typeof part.arguments === 'string') {
|
|
return part.arguments as string;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
const text = String(value);
|
|
return forceReasoning ? `<think>${text}</think>` : text;
|
|
}
|
|
|
|
return '';
|
|
};
|
|
|
|
export const parseAssistantContent = (
|
|
content: unknown,
|
|
reasoningField?: unknown,
|
|
): ParsedAssistantContent => {
|
|
let rawContent = serializeContent(content);
|
|
const { display: baseDisplay, reasoning: baseReasoning } = stripThinkingTags(rawContent);
|
|
|
|
let display = baseDisplay;
|
|
const reasoningSegments: string[] = [];
|
|
if (baseReasoning) {
|
|
reasoningSegments.push(baseReasoning);
|
|
}
|
|
|
|
let reasoningRaw = serializeContent(reasoningField);
|
|
if (reasoningRaw) {
|
|
const { display: fallbackDisplay, reasoning: fallbackReasoning } = stripThinkingTags(reasoningRaw);
|
|
if (fallbackReasoning) {
|
|
reasoningSegments.push(fallbackReasoning);
|
|
}
|
|
if (!display && fallbackDisplay) {
|
|
display = fallbackDisplay;
|
|
}
|
|
if (!rawContent) {
|
|
rawContent = reasoningRaw;
|
|
}
|
|
}
|
|
|
|
if (!display && rawContent) {
|
|
display = rawContent.trim();
|
|
}
|
|
|
|
const distinctReasoning = Array.from(
|
|
new Set(
|
|
reasoningSegments
|
|
.flatMap((segment) => segment.split('\n\n'))
|
|
.map((segment) => segment.trim())
|
|
.filter(Boolean),
|
|
),
|
|
);
|
|
|
|
if (display) {
|
|
let cleanedDisplay = display;
|
|
distinctReasoning.forEach((segment) => {
|
|
if (!segment) return;
|
|
const escaped = segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
const regex = new RegExp(escaped, 'gi');
|
|
cleanedDisplay = cleanedDisplay.replace(regex, '');
|
|
});
|
|
cleanedDisplay = cleanedDisplay.replace(/<\/?think>/gi, '').trim();
|
|
display = cleanedDisplay || display;
|
|
}
|
|
|
|
let rawForConversation = rawContent;
|
|
const reasoningText = distinctReasoning.join('\n\n');
|
|
if (reasoningText) {
|
|
const hasOpeningThink = rawContent.includes('<think>');
|
|
const hasClosingThink = rawContent.includes('</think>');
|
|
if (!hasOpeningThink || !hasClosingThink) {
|
|
const displaySection = display ? `\n\n${display}` : '';
|
|
rawForConversation = `<think>${reasoningText}</think>${displaySection}`;
|
|
}
|
|
}
|
|
|
|
return {
|
|
raw: rawForConversation,
|
|
display: display || '',
|
|
reasoning: distinctReasoning.length ? distinctReasoning.join('\n\n') : undefined,
|
|
};
|
|
};
|