mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-08 02:15:27 +08:00
186 lines
4.3 KiB
JavaScript
186 lines
4.3 KiB
JavaScript
'use strict';
|
|
|
|
// Keep in sync with Go toolSieveContextTailLimit.
|
|
const TOOL_SIEVE_CONTEXT_TAIL_LIMIT = 2048;
|
|
|
|
function createToolSieveState() {
|
|
return {
|
|
pending: '',
|
|
capture: '',
|
|
capturing: false,
|
|
recentTextTail: '',
|
|
codeFenceStack: [],
|
|
codeFencePendingTicks: 0,
|
|
codeFenceLineStart: true,
|
|
pendingToolRaw: '',
|
|
pendingToolCalls: [],
|
|
disableDeltas: false,
|
|
toolNameSent: false,
|
|
toolName: '',
|
|
toolArgsStart: -1,
|
|
toolArgsSent: -1,
|
|
toolArgsString: false,
|
|
toolArgsDone: false,
|
|
};
|
|
}
|
|
|
|
function resetIncrementalToolState(state) {
|
|
state.disableDeltas = false;
|
|
state.toolNameSent = false;
|
|
state.toolName = '';
|
|
state.toolArgsStart = -1;
|
|
state.toolArgsSent = -1;
|
|
state.toolArgsString = false;
|
|
state.toolArgsDone = false;
|
|
}
|
|
|
|
function noteText(state, text) {
|
|
if (!state || !hasMeaningfulText(text)) {
|
|
return;
|
|
}
|
|
updateCodeFenceState(state, text);
|
|
state.recentTextTail = appendTail(state.recentTextTail, text, TOOL_SIEVE_CONTEXT_TAIL_LIMIT);
|
|
}
|
|
|
|
function appendTail(prev, next, max) {
|
|
const left = typeof prev === 'string' ? prev : '';
|
|
const right = typeof next === 'string' ? next : '';
|
|
if (!Number.isFinite(max) || max <= 0) {
|
|
return '';
|
|
}
|
|
const combined = left + right;
|
|
if (combined.length <= max) {
|
|
return combined;
|
|
}
|
|
return combined.slice(combined.length - max);
|
|
}
|
|
|
|
function looksLikeToolExampleContext(text) {
|
|
return insideCodeFence(text);
|
|
}
|
|
|
|
function insideCodeFence(text) {
|
|
const t = typeof text === 'string' ? text : '';
|
|
if (!t) {
|
|
return false;
|
|
}
|
|
const ticks = (t.match(/```/g) || []).length;
|
|
return ticks % 2 === 1;
|
|
}
|
|
|
|
function insideCodeFenceWithState(state, text) {
|
|
if (!state) {
|
|
return insideCodeFence(text);
|
|
}
|
|
const simulated = simulateCodeFenceState(
|
|
Array.isArray(state.codeFenceStack) ? state.codeFenceStack : [],
|
|
Number.isInteger(state.codeFencePendingTicks) ? state.codeFencePendingTicks : 0,
|
|
state.codeFenceLineStart !== false,
|
|
text,
|
|
);
|
|
return simulated.stack.length > 0;
|
|
}
|
|
|
|
function updateCodeFenceState(state, text) {
|
|
if (!state) {
|
|
return;
|
|
}
|
|
const next = simulateCodeFenceState(
|
|
Array.isArray(state.codeFenceStack) ? state.codeFenceStack : [],
|
|
Number.isInteger(state.codeFencePendingTicks) ? state.codeFencePendingTicks : 0,
|
|
state.codeFenceLineStart !== false,
|
|
text,
|
|
);
|
|
state.codeFenceStack = next.stack;
|
|
state.codeFencePendingTicks = next.pendingTicks;
|
|
state.codeFenceLineStart = next.lineStart;
|
|
}
|
|
|
|
function simulateCodeFenceState(stack, pendingTicks, lineStart, text) {
|
|
const chunk = typeof text === 'string' ? text : '';
|
|
const nextStack = Array.isArray(stack) ? [...stack] : [];
|
|
let ticks = Number.isInteger(pendingTicks) ? pendingTicks : 0;
|
|
let atLineStart = lineStart !== false;
|
|
|
|
const flushTicks = () => {
|
|
if (ticks > 0) {
|
|
if (atLineStart && ticks >= 3) {
|
|
applyFenceMarker(nextStack, ticks);
|
|
}
|
|
atLineStart = false;
|
|
ticks = 0;
|
|
}
|
|
};
|
|
|
|
for (let i = 0; i < chunk.length; i += 1) {
|
|
const ch = chunk[i];
|
|
if (ch === '`') {
|
|
ticks += 1;
|
|
continue;
|
|
}
|
|
flushTicks();
|
|
if (ch === '\n' || ch === '\r') {
|
|
atLineStart = true;
|
|
continue;
|
|
}
|
|
if ((ch === ' ' || ch === '\t') && atLineStart) {
|
|
continue;
|
|
}
|
|
atLineStart = false;
|
|
}
|
|
// keep ticks for cross-chunk continuation.
|
|
return {
|
|
stack: nextStack,
|
|
pendingTicks: ticks,
|
|
lineStart: atLineStart,
|
|
};
|
|
}
|
|
|
|
function applyFenceMarker(stack, ticks) {
|
|
if (!Array.isArray(stack)) {
|
|
return;
|
|
}
|
|
if (stack.length === 0) {
|
|
stack.push(ticks);
|
|
return;
|
|
}
|
|
const top = stack[stack.length - 1];
|
|
if (ticks >= top) {
|
|
stack.pop();
|
|
return;
|
|
}
|
|
// nested/open inner fence using longer marker for robustness.
|
|
stack.push(ticks);
|
|
}
|
|
|
|
function hasMeaningfulText(text) {
|
|
return toStringSafe(text) !== '';
|
|
}
|
|
|
|
function toStringSafe(v) {
|
|
if (typeof v === 'string') {
|
|
return v.trim();
|
|
}
|
|
if (Array.isArray(v)) {
|
|
return toStringSafe(v[0]);
|
|
}
|
|
if (v == null) {
|
|
return '';
|
|
}
|
|
return String(v).trim();
|
|
}
|
|
|
|
module.exports = {
|
|
TOOL_SIEVE_CONTEXT_TAIL_LIMIT,
|
|
createToolSieveState,
|
|
resetIncrementalToolState,
|
|
noteText,
|
|
appendTail,
|
|
looksLikeToolExampleContext,
|
|
insideCodeFence,
|
|
insideCodeFenceWithState,
|
|
updateCodeFenceState,
|
|
hasMeaningfulText,
|
|
toStringSafe,
|
|
};
|