






























1+export const END_TOOL_REQUEST = "[END_TOOL_REQUEST]";
2+export const HARMONY_CHANNEL_MARKER = "<|channel|>";
3+export const HARMONY_MESSAGE_MARKER = "<|message|>";
4+export const HARMONY_CALL_MARKER = "<|call|>";
5+6+export function matchesLiteralPrefix(text: string, literal: string): boolean {
7+return literal.startsWith(text) || text.startsWith(literal);
8+}
9+10+export function isPlainTextToolNameChar(char: string | undefined): boolean {
11+return Boolean(char && /[A-Za-z0-9_-]/.test(char));
12+}
13+14+export function isXmlishNameChar(char: string | undefined): boolean {
15+return Boolean(char && /[A-Za-z0-9_.:-]/.test(char));
16+}
17+18+export function skipHorizontalWhitespace(text: string, start: number): number {
19+let index = start;
20+while (index < text.length && (text[index] === " " || text[index] === "\t")) {
21+index += 1;
22+}
23+return index;
24+}
25+26+export function skipWhitespace(text: string, start: number): number {
27+let index = start;
28+while (index < text.length && /\s/.test(text[index] ?? "")) {
29+index += 1;
30+}
31+return index;
32+}
33+34+export function consumeLineBreak(text: string, start: number): number | null {
35+if (text[start] === "\r") {
36+return text[start + 1] === "\n" ? start + 2 : start + 1;
37+}
38+if (text[start] === "\n") {
39+return start + 1;
40+}
41+return null;
42+}
43+44+export function findJsonObjectEnd(
45+text: string,
46+start: number,
47+maxPayloadBytes?: number,
48+): number | null {
49+let depth = 0;
50+let inString = false;
51+let escaped = false;
52+for (let index = start; index < text.length; index += 1) {
53+if (maxPayloadBytes !== undefined && index + 1 - start > maxPayloadBytes) {
54+return null;
55+}
56+const char = text[index];
57+if (inString) {
58+if (escaped) {
59+escaped = false;
60+} else if (char === "\\") {
61+escaped = true;
62+} else if (char === '"') {
63+inString = false;
64+}
65+continue;
66+}
67+if (char === '"') {
68+inString = true;
69+continue;
70+}
71+if (char === "{") {
72+depth += 1;
73+continue;
74+}
75+if (char === "}") {
76+depth -= 1;
77+if (depth === 0) {
78+return index + 1;
79+}
80+}
81+}
82+return null;
83+}
84+85+export function skipSerializedToolCallTrailingLineBreak(text: string, cursor: number): number {
86+const afterLineBreak = consumeLineBreak(text, cursor);
87+return afterLineBreak ?? cursor;
88+}
89+90+export function consumeJsonToolClosingMarker(text: string, cursor: number): number {
91+let markerStart = cursor;
92+while (markerStart < text.length && /\s/.test(text[markerStart] ?? "")) {
93+markerStart += 1;
94+}
95+const rest = text.slice(markerStart);
96+if (rest.startsWith(END_TOOL_REQUEST)) {
97+return skipSerializedToolCallTrailingLineBreak(text, markerStart + END_TOOL_REQUEST.length);
98+}
99+const bracketClose = /^\[\/[A-Za-z0-9_-]+\]/.exec(rest);
100+if (bracketClose) {
101+return skipSerializedToolCallTrailingLineBreak(text, markerStart + bracketClose[0].length);
102+}
103+if (rest.startsWith(HARMONY_CALL_MARKER)) {
104+return skipSerializedToolCallTrailingLineBreak(text, markerStart + HARMONY_CALL_MARKER.length);
105+}
106+return skipSerializedToolCallTrailingLineBreak(text, cursor);
107+}
108+109+export function findBracketedJsonPayloadStart(text: string): number | null {
110+if (!text.startsWith("[")) {
111+return null;
112+}
113+const close = text.indexOf("]");
114+if (close === -1) {
115+return null;
116+}
117+let cursor = close + 1;
118+cursor = skipHorizontalWhitespace(text, cursor);
119+cursor = skipSerializedToolCallTrailingLineBreak(text, cursor);
120+cursor = skipHorizontalWhitespace(text, cursor);
121+return text[cursor] === "{" ? cursor : null;
122+}
123+124+export function findHarmonyJsonPayloadStart(text: string): number | null {
125+let cursor = 0;
126+if (text.startsWith(HARMONY_CHANNEL_MARKER)) {
127+cursor = HARMONY_CHANNEL_MARKER.length;
128+}
129+const rest = text.slice(cursor);
130+const channel = ["commentary", "analysis", "final"].find((candidate) =>
131+rest.startsWith(candidate),
132+);
133+if (!channel) {
134+return null;
135+}
136+cursor += channel.length;
137+cursor = skipHorizontalWhitespace(text, cursor);
138+if (!text.slice(cursor).startsWith("to=")) {
139+return null;
140+}
141+cursor += "to=".length;
142+const nameStart = cursor;
143+while (isPlainTextToolNameChar(text[cursor])) {
144+cursor += 1;
145+}
146+if (cursor === nameStart) {
147+return null;
148+}
149+cursor = skipHorizontalWhitespace(text, cursor);
150+if (!text.slice(cursor).startsWith("code")) {
151+return null;
152+}
153+cursor += "code".length;
154+cursor = skipWhitespace(text, cursor);
155+if (text.slice(cursor).startsWith(HARMONY_MESSAGE_MARKER)) {
156+cursor = skipWhitespace(text, cursor + HARMONY_MESSAGE_MARKER.length);
157+}
158+return text[cursor] === "{" ? cursor : null;
159+}
160+161+export function startsWithAsciiMarkerIgnoreCase(
162+text: string,
163+cursor: number,
164+marker: string,
165+): boolean {
166+return text.slice(cursor, cursor + marker.length).toLowerCase() === marker;
167+}
168+169+export function indexOfAsciiMarkerIgnoreCase(text: string, marker: string, start: number): number {
170+let cursor = start;
171+while (cursor < text.length) {
172+const next = text.indexOf(marker[0] ?? "", cursor);
173+if (next === -1) {
174+return -1;
175+}
176+if (startsWithAsciiMarkerIgnoreCase(text, next, marker)) {
177+return next;
178+}
179+cursor = next + 1;
180+}
181+return -1;
182+}
183+184+export function findXmlishToolCallEnd(text: string): number | null {
185+let cursor: number;
186+const xmlFunction = /^<function=[A-Za-z0-9_.:-]+>/i.exec(text);
187+if (xmlFunction) {
188+cursor = xmlFunction[0].length;
189+} else {
190+const bracketed = /^\[(?:tool:)?[A-Za-z0-9_-]+\]/.exec(text);
191+if (!bracketed) {
192+return null;
193+}
194+cursor = bracketed[0].length;
195+cursor = skipHorizontalWhitespace(text, cursor);
196+cursor = skipSerializedToolCallTrailingLineBreak(text, cursor);
197+}
198+199+cursor = skipWhitespace(text, cursor);
200+if (!startsWithAsciiMarkerIgnoreCase(text, cursor, "<parameter=")) {
201+return null;
202+}
203+204+while (cursor < text.length) {
205+const parameterClose = indexOfAsciiMarkerIgnoreCase(text, "</parameter>", cursor);
206+if (parameterClose === -1) {
207+return null;
208+}
209+cursor = skipWhitespace(text, parameterClose + "</parameter>".length);
210+if (startsWithAsciiMarkerIgnoreCase(text, cursor, "</function>")) {
211+return skipSerializedToolCallTrailingLineBreak(text, cursor + "</function>".length);
212+}
213+if (!startsWithAsciiMarkerIgnoreCase(text, cursor, "<parameter=")) {
214+return skipSerializedToolCallTrailingLineBreak(text, cursor);
215+}
216+}
217+return null;
218+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。