@pedrofariasx/qwenproxy 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +3 -13
  2. package/package.json +1 -1
  3. package/src/api/server.ts +4 -6
  4. package/src/cache/memory-cache.ts +5 -3
  5. package/src/core/account-manager.ts +1 -1
  6. package/src/core/accounts.ts +1 -1
  7. package/src/login.ts +2 -2
  8. package/src/routes/chat.ts +122 -91
  9. package/src/routes/upload.ts +5 -5
  10. package/src/services/playwright.ts +40 -120
  11. package/src/services/qwen.ts +29 -27
  12. package/src/tests/concurrency.test.ts +1 -1
  13. package/src/tests/concurrentChat.test.ts +1 -1
  14. package/src/tests/contextTruncation.test.ts +142 -0
  15. package/src/tests/delta.test.ts +80 -10
  16. package/src/tests/jsonFix.test.ts +110 -98
  17. package/src/tests/multimodal.test.ts +1 -1
  18. package/src/tests/parser.test.ts +40 -2
  19. package/src/tools/parser.ts +98 -33
  20. package/src/utils/context-truncation.ts +1 -6
  21. package/src/utils/json.ts +9 -8
  22. package/src/utils/types.ts +1 -1
  23. package/src/linter/extraction-engine.ts +0 -165
  24. package/src/linter/index.ts +0 -258
  25. package/src/linter/repair-normalize.ts +0 -245
  26. package/src/linter/safety-gate.ts +0 -219
  27. package/src/linter/streaming-state-machine.ts +0 -252
  28. package/src/linter/structural-parser.ts +0 -352
  29. package/src/linter/types.ts +0 -74
  30. package/src/tests/linter.test.ts +0 -151
  31. package/src/tests/parallel.test.ts +0 -42
  32. package/src/tests/structureVerification.test.ts +0 -176
  33. package/src/tools/ast.ts +0 -15
  34. package/src/tools/coercion.ts +0 -67
  35. package/src/tools/confidence.ts +0 -48
  36. package/src/tools/detector.ts +0 -40
  37. package/src/tools/executor.ts +0 -236
  38. package/src/tools/pipeline.ts +0 -122
  39. package/src/tools/registry-runtime.ts +0 -34
  40. package/src/tools/repair.ts +0 -42
  41. package/src/tools/validator.ts +0 -33
@@ -1,8 +1,3 @@
1
- export interface TruncatedMessage {
2
- role: string;
3
- content: string;
4
- }
5
-
6
1
  export function estimateTokenCount(text: string): number {
7
2
  // Divisor conservador (2.5) para evitar estouro silencioso do context window.
8
3
  // Tokenizers modernos (como o do Qwen) usam ~1.5 a 2.5 caracteres por token
@@ -36,7 +31,7 @@ function truncateSemantically(content: string, maxChars: number): string {
36
31
  }
37
32
 
38
33
  export function truncateMessages(
39
- messages: Array<{ role: string; content: string | null | any[] }>,
34
+ messages: Array<{ role: string; content: string | null | any[] | Record<string, unknown> }>,
40
35
  maxContextLength: number,
41
36
  systemPrompt: string = ''
42
37
  ): Array<{ role: string; content: string }> {
package/src/utils/json.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Robust JSON parsing utilities
5
5
  */
6
6
 
7
- function sanitizeAndBalance(input: string): { result: string; openBraces: number; openBrackets: number } {
7
+ function sanitizeAndBalance(input: string): { result: string; openBraces: number; openBrackets: number; inString: boolean } {
8
8
  let out = '';
9
9
  let openBraces = 0;
10
10
  let openBrackets = 0;
@@ -48,11 +48,12 @@ function sanitizeAndBalance(input: string): { result: string; openBraces: number
48
48
  if (char === ']') openBrackets--;
49
49
  }
50
50
  }
51
- return { result: out, openBraces, openBrackets };
51
+ return { result: out, openBraces, openBrackets, inString };
52
52
  }
53
53
 
54
- function closeBraces(input: string, openBraces: number, openBrackets: number): string {
54
+ function closeBraces(input: string, openBraces: number, openBrackets: number, inString: boolean = false): string {
55
55
  let out = input;
56
+ if (inString) out += '"';
56
57
  if (openBrackets > 0) out += ']'.repeat(openBrackets);
57
58
  if (openBraces > 0) out += '}'.repeat(openBraces);
58
59
  return out;
@@ -79,7 +80,7 @@ export function robustParseJSON(str: string): any {
79
80
  cleaned = cleaned.slice(0, -1).trim();
80
81
  }
81
82
 
82
- const { result: fixedJson, openBraces, openBrackets } = sanitizeAndBalance(cleaned);
83
+ const { result: fixedJson, openBraces, openBrackets, inString } = sanitizeAndBalance(cleaned);
83
84
  let lastBalancedIndex = -1;
84
85
 
85
86
  { let ob = 0, bk = 0, ins = false, esc = false;
@@ -99,15 +100,15 @@ export function robustParseJSON(str: string): any {
99
100
  let tempJson = fixedJson;
100
101
  if (lastBalancedIndex !== -1 && (openBraces !== 0 || openBrackets !== 0 || fixedJson.length > lastBalancedIndex + 1)) {
101
102
  tempJson = fixedJson.substring(0, lastBalancedIndex + 1);
102
- } else if (openBraces > 0 || openBrackets > 0) {
103
- tempJson = closeBraces(fixedJson, openBraces, openBrackets);
103
+ } else if (openBraces > 0 || openBrackets > 0 || inString) {
104
+ tempJson = closeBraces(fixedJson, openBraces, openBrackets, inString);
104
105
  }
105
106
 
106
107
  try { return JSON.parse(tempJson); } catch (e) {
107
108
  let aggressive = fixedJson.trim();
108
109
  if (aggressive.endsWith(',')) aggressive = aggressive.slice(0, -1);
109
- const { result: aggFixed, openBraces: ob, openBrackets: bk } = sanitizeAndBalance(aggressive);
110
- try { return JSON.parse(closeBraces(aggFixed, ob, bk)); } catch {
110
+ const { result: aggFixed, openBraces: ob, openBrackets: bk, inString: aggInString } = sanitizeAndBalance(aggressive);
111
+ try { return JSON.parse(closeBraces(aggFixed, ob, bk, aggInString)); } catch {
111
112
  return null;
112
113
  }
113
114
  }
@@ -8,7 +8,7 @@
8
8
  * Modified By: Pedro Farias
9
9
  */
10
10
 
11
- import type { JsonSchema, FunctionToolDefinition } from '../tools/types.ts';
11
+ import type { JsonSchema, FunctionToolDefinition } from '../tools/types.js';
12
12
  export type { JsonSchema, FunctionToolDefinition };
13
13
 
14
14
  /** Tool choice options */
@@ -1,165 +0,0 @@
1
- /*
2
- * Layer 3: Tool Extraction Engine (Multi-Format)
3
- */
4
-
5
- import type { ToolCallSource, RawToolCandidate, SecurityViolation } from './types'
6
- import { StructuralParser } from './structural-parser'
7
-
8
- export interface ExtractionResult {
9
- candidates: RawToolCandidate[]
10
- sourceHint: ToolCallSource
11
- extractionErrors: string[]
12
- }
13
-
14
- export class ToolExtractionEngine {
15
- private parser: StructuralParser
16
-
17
- constructor() {
18
- this.parser = new StructuralParser()
19
- }
20
-
21
- extract(input: string): ExtractionResult {
22
- const candidates: RawToolCandidate[] = []
23
- const errors: string[] = []
24
- let sourceHint: ToolCallSource = this.detectSourceHint(input)
25
-
26
- const jsonCandidates = this.extractJsonObjects(input)
27
- for (const candidate of jsonCandidates) {
28
- const parsed = this.tryParseJson(candidate.raw)
29
- if (parsed) {
30
- candidates.push({
31
- source: candidate.sourceHint,
32
- raw: parsed,
33
- rawString: candidate.raw,
34
- confidence: this.calculateJsonConfidence(parsed, candidate.sourceHint),
35
- })
36
- }
37
- }
38
-
39
- if (candidates.length === 0) {
40
- const reactCandidates = this.extractReAct(input)
41
- candidates.push(
42
- ...reactCandidates.map(c => ({
43
- source: 'react' as ToolCallSource,
44
- raw: c.raw,
45
- rawString: c.rawString,
46
- confidence: 0.8,
47
- }))
48
- )
49
- }
50
-
51
- if (candidates.length === 0) {
52
- const gemini = this.extractGemini(input)
53
- if (gemini) {
54
- candidates.push({
55
- source: 'gemini' as ToolCallSource,
56
- raw: gemini,
57
- rawString: JSON.stringify(gemini),
58
- confidence: 0.85,
59
- })
60
- }
61
- }
62
-
63
- return { candidates, sourceHint, extractionErrors: errors }
64
- }
65
-
66
- private extractJsonObjects(text: string): Array<{ raw: string; sourceHint: ToolCallSource }> {
67
- const results: Array<{ raw: string; sourceHint: ToolCallSource }> = []
68
- const markupCleaned = this.stripMarkup(text)
69
- const jsonSpans = StructuralParser.extractJsonFromText(markupCleaned)
70
-
71
- for (const span of jsonSpans) {
72
- const sourceHint = this.detectSourceHint(span)
73
- results.push({ raw: span, sourceHint })
74
- }
75
-
76
- if (results.length === 0) {
77
- const funcMatch = markupCleaned.match(/call_function\s*\(\s*'(\w+)'\s*,\s*(\{[\s\S]*\})\s*\)/i)
78
- if (funcMatch) {
79
- results.push({ raw: `{"name":"${funcMatch[1]}","arguments":${funcMatch[2]}}`, sourceHint: 'openai' })
80
- }
81
- }
82
-
83
- return results
84
- }
85
-
86
- private extractReAct(text: string): Array<{ raw: Record<string, unknown>; rawString: string }> {
87
- const results: Array<{ raw: Record<string, unknown>; rawString: string }> = []
88
- const patterns = [
89
- /Action:\s*(\w+)\s*\n?\s*Action Input:\s*(\{[\s\S]*?)(?=\n\s*\n|\n\s*Observation|\n\s*Final Answer|$)/i,
90
- /Action:\s*(\w+)\s+Action Input:\s*(\{[\s\S]*)/i,
91
- /\*\*Action\*\*:\s*(\w+)\s*\n?\s*\*\*Action Input\*\*:\s*(\{[\s\S]*?)(?=\n\s*\n|\n\s*Observation|\n\s*Final Answer|$)/i,
92
- ]
93
-
94
- for (const pattern of patterns) {
95
- const match = text.match(pattern)
96
- if (match) {
97
- let actionInputStr = match[2].trim()
98
- actionInputStr = actionInputStr.replace(/\n\s*Observation.*$/is, '').trim()
99
- results.push({
100
- raw: { name: match[1], arguments: this.safeParse(actionInputStr) || {} },
101
- rawString: match[0],
102
- })
103
- break
104
- }
105
- }
106
-
107
- return results
108
- }
109
-
110
- private extractGemini(text: string): Record<string, unknown> | null {
111
- const patterns = [
112
- /functionCall\s*[:=]\s*\{\s*name\s*[:=]\s*['"]([^'"]+)['"]\s*,\s*args\s*[:=]\s*(\{[\s\S]*\})\s*\}/i,
113
- /"functionCall"\s*:\s*\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"args"\s*:\s*(\{[\s\S]*?\})\s*\}/i,
114
- ]
115
- for (const pattern of patterns) {
116
- const match = text.match(pattern)
117
- if (match) return { name: match[1], arguments: this.safeParse(match[2]) || {} }
118
- }
119
- return null
120
- }
121
-
122
- private stripMarkup(text: string): string {
123
- return text
124
- .replace(/```json\s*/gi, '')
125
- .replace(/```\s*/gi, '')
126
- .replace(/<function_calls>/gi, '')
127
- .replace(/<\/function_calls>/gi, '')
128
- .trim()
129
- }
130
-
131
- private detectSourceHint(text: string): ToolCallSource {
132
- const t = this.stripMarkup(text)
133
- if (/\btool_use\b/.test(t) || /type:\s*['"]?tool_use/i.test(t)) return 'claude'
134
- if (/\bfunctionCall\b/.test(t) || /"functionCall"/.test(t)) return 'gemini'
135
- if (/\barguments\b/.test(t) && /\bname\b/.test(t)) return 'openai'
136
- if (/Action:\s*\w+\s*\n?Action Input:/i.test(t)) return 'react'
137
- return 'unknown'
138
- }
139
-
140
- private tryParseJson(raw: string): Record<string, unknown> | null {
141
- try { return JSON.parse(raw) } catch {
142
- const result = this.parser.parse(raw, 'unknown')
143
- return (result.ast?.value as Record<string, unknown>) ?? null
144
- }
145
- }
146
-
147
- private safeParse(str: string): Record<string, unknown> | null {
148
- try { return JSON.parse(str) } catch {
149
- const result = this.parser.parse(str, 'unknown')
150
- return (result.ast?.value as Record<string, unknown>) ?? null
151
- }
152
- }
153
-
154
- private calculateJsonConfidence(parsed: Record<string, unknown>, source: ToolCallSource): number {
155
- let conf = 0.5
156
- if (parsed.name) conf += 0.15
157
- if (parsed.arguments && typeof parsed.arguments === 'object') conf += 0.15
158
- if (parsed.input && typeof parsed.input === 'object') conf += 0.1
159
- if (parsed.tool) conf += 0.1
160
- if (parsed.args && typeof parsed.args === 'object') conf += 0.1
161
- if (typeof parsed.arguments === 'string') conf -= 0.2
162
- if (typeof parsed.input === 'string') conf -= 0.2
163
- return Math.min(0.95, Math.max(0.3, conf))
164
- }
165
- }
@@ -1,258 +0,0 @@
1
- /*
2
- * UltraToolCallLinter v1.0
3
- * Main public API: composable 5-layer pipeline
4
- */
5
-
6
- import type {
7
- CanonicalToolCall,
8
- ParserState,
9
- RawToolCandidate,
10
- ParseResult,
11
- ToolCallSource,
12
- SecurityViolation,
13
- ToolDefinition,
14
- ToolRegistry,
15
- } from './types'
16
-
17
- import { StreamingStateMachine } from './streaming-state-machine'
18
- import { StructuralParser } from './structural-parser'
19
- import { ToolExtractionEngine } from './extraction-engine'
20
- import { GrammarRepairEngine, NormalizationEngine } from './repair-normalize'
21
- import { SafetyGate } from './safety-gate'
22
-
23
- export interface LinterConfig {
24
- registry?: ToolRegistry
25
- strictMode?: boolean
26
- enableSecurityGate?: boolean
27
- maxRecoveryAttempts?: number
28
- minConfidenceThreshold?: number
29
- }
30
-
31
- export class UltraToolCallLinter {
32
- private readonly streaming = new StreamingStateMachine()
33
- private readonly extractor = new ToolExtractionEngine()
34
- private readonly repairer = new GrammarRepairEngine()
35
- private readonly normalizer = new NormalizationEngine()
36
- private readonly gate = new SafetyGate()
37
-
38
- private registry: ToolRegistry = {}
39
- private strictMode: boolean = false
40
- private enableSecurityGate: boolean = true
41
- private maxRecoveryAttempts: number = 3
42
- private minConfidenceThreshold: number = 0.3
43
-
44
- constructor(config: LinterConfig = {}) {
45
- if (config.registry) this.registry = config.registry
46
- if (config.strictMode !== undefined) this.strictMode = config.strictMode
47
- if (config.enableSecurityGate !== undefined) this.enableSecurityGate = config.enableSecurityGate
48
- if (config.maxRecoveryAttempts !== undefined) this.maxRecoveryAttempts = config.maxRecoveryAttempts
49
- if (config.minConfidenceThreshold !== undefined) this.minConfidenceThreshold = config.minConfidenceThreshold
50
-
51
- if (this.registry) this.gate.registerRegistry(this.registry)
52
- }
53
-
54
- setRegistry(registry: ToolRegistry): void {
55
- this.registry = registry
56
- this.gate.registerRegistry(registry)
57
- }
58
-
59
- registerTool(name: string, def: ToolDefinition): void {
60
- this.gate.registerTool(name, def)
61
- }
62
-
63
- push(chunk: string): void {
64
- this.streaming.push(chunk)
65
- }
66
-
67
- parse(): ParseResult {
68
- const buffer = this.streaming.getBuffer()
69
- const errors: string[] = []
70
-
71
- const { candidates, extractionErrors } = this.extractor.extract(buffer)
72
- errors.push(...extractionErrors)
73
-
74
- const toolCalls: CanonicalToolCall[] = []
75
- let maxConfidence = 0
76
-
77
- for (const candidate of candidates) {
78
- const result = this.processCandidate(candidate, errors)
79
- const tc: CanonicalToolCall = {
80
- tool: result.tool,
81
- input: result.input,
82
- meta: result.meta ?? { source: candidate.source, confidence: 0, repaired: false },
83
- }
84
- if (tc.meta) {
85
- tc.meta.confidence = result.meta?.confidence ?? 0
86
- tc.meta.repaired = result.meta?.repaired ?? false
87
- } else {
88
- tc.meta = { source: candidate.source, confidence: result.meta?.confidence ?? 0, repaired: result.meta?.repaired ?? false }
89
- }
90
- toolCalls.push(tc)
91
- maxConfidence = Math.max(maxConfidence, tc.meta.confidence)
92
- }
93
-
94
- this.streaming.reset()
95
-
96
- return {
97
- text: buffer,
98
- toolCalls,
99
- errors,
100
- confidence: maxConfidence,
101
- }
102
- }
103
-
104
- parseText(text: string): ParseResult {
105
- this.streaming.reset()
106
- this.streaming.push(text)
107
- return this.parse()
108
- }
109
-
110
- parseObject(name: string, argumentsObj: Record<string, unknown>): ParseResult {
111
- const candidate: RawToolCandidate = {
112
- source: 'openai',
113
- raw: { name, arguments: argumentsObj },
114
- rawString: JSON.stringify({ name, arguments: argumentsObj }),
115
- confidence: 1,
116
- }
117
-
118
- const errors: string[] = []
119
- const processingResult = this.processCandidate(candidate, errors)
120
-
121
- return {
122
- text: '',
123
- toolCalls: [processingResult],
124
- errors,
125
- confidence: processingResult.meta?.confidence ?? 0,
126
- }
127
- }
128
-
129
- repair(input: string): string {
130
- const result = this.repairer.repair(input)
131
- if (!result.repaired) {
132
- const structural = new StructuralParser()
133
- const parsed = structural.parse(input, 'unknown')
134
- if (parsed.ast?.value) return JSON.stringify(parsed.ast.value, null, 0)
135
- }
136
- return JSON.stringify(result.value ?? {}, null, 0)
137
- }
138
-
139
- extract(input: string): RawToolCandidate[] {
140
- this.streaming.reset()
141
- const { candidates } = this.extractor.extract(input)
142
- return candidates
143
- }
144
-
145
- reset(): void {
146
- this.streaming.reset()
147
- }
148
-
149
- getState(): ParserState {
150
- return this.streaming.getState()
151
- }
152
-
153
- public processCandidate(
154
- candidate: RawToolCandidate,
155
- errors: string[]
156
- ): CanonicalToolCall {
157
- let confidence = candidate.confidence
158
- let repaired = false
159
- let currentRaw = { ...candidate.raw } as Record<string, unknown>
160
-
161
- const args = currentRaw.arguments ?? currentRaw.input ?? currentRaw.args
162
-
163
- if (typeof args === 'string' && args.trim().length > 0) {
164
- const repairResult = this.repairer.repair(args)
165
- if (repairResult.repaired) {
166
- currentRaw = { ...currentRaw, arguments: repairResult.value }
167
- repaired = true
168
- confidence = Math.min(confidence + repairResult.confidence * 0.1, 0.95)
169
- this.recoveryLog('string-args-repaired', candidate, repairResult.strategy)
170
- }
171
- }
172
-
173
- if (!currentRaw.tool && !currentRaw.name && !currentRaw.functionCall && !currentRaw.type) {
174
- const repairResult = this.repairer.repair(candidate.rawString)
175
- if (repairResult.repaired) {
176
- currentRaw = repairResult.value as Record<string, unknown>
177
- repaired = true
178
- confidence = Math.min(confidence + 0.05, 0.9)
179
- this.recoveryLog('synthetic-construction', candidate, repairResult.strategy)
180
- }
181
- }
182
-
183
- const { toolCall, warnings } = this.normalizer.normalize(
184
- { source: candidate.source, raw: currentRaw, rawString: candidate.rawString, confidence },
185
- repaired
186
- )
187
-
188
- if (warnings.length > 0) errors.push(...warnings)
189
-
190
- if (!toolCall.meta) {
191
- toolCall.meta = { source: candidate.source, confidence, repaired }
192
- } else {
193
- toolCall.meta.confidence = confidence
194
- toolCall.meta.repaired = repaired
195
- }
196
-
197
- if (this.enableSecurityGate) {
198
- const report = this.gate.validate(toolCall, candidate.source)
199
- toolCall.meta.confidence = Math.min(toolCall.meta.confidence, report.confidence)
200
-
201
- if (!report.isValid && !this.strictMode) {
202
- const recoveryResult = this.attemptRecovery(candidate, report.violations, toolCall, errors)
203
- if (recoveryResult) return recoveryResult
204
- }
205
-
206
- errors.push(...report.warnings)
207
- }
208
-
209
- return toolCall
210
- }
211
-
212
- private attemptRecovery(
213
- candidate: RawToolCandidate,
214
- violations: SecurityViolation[],
215
- failedCall: CanonicalToolCall,
216
- errors: string[]
217
- ): CanonicalToolCall | null {
218
- let attempt = 0
219
- const currentCall: CanonicalToolCall = {
220
- tool: failedCall.tool,
221
- input: { ...failedCall.input },
222
- meta: failedCall.meta ? { ...failedCall.meta } : undefined,
223
- }
224
-
225
- while (attempt < this.maxRecoveryAttempts) {
226
- for (const v of violations) {
227
- if (v.field === 'input') {
228
- currentCall.input = {}
229
- errors.push(`Recovered: cleared entire input`)
230
- break
231
- }
232
- if (v.field in currentCall.input) {
233
- delete currentCall.input[v.field]
234
- errors.push(`Recovered: removed invalid field "${v.field}"`)
235
- }
236
- }
237
-
238
- const report = this.gate.validate(currentCall, candidate.source)
239
- if (report.isValid) {
240
- if (currentCall.meta) {
241
- currentCall.meta.repaired = true
242
- currentCall.meta.confidence = Math.max(0.3, report.confidence)
243
- }
244
- return currentCall
245
- }
246
-
247
- attempt++
248
- }
249
-
250
- return null
251
- }
252
-
253
- private recoveryLog(event: string, candidate: RawToolCandidate, strategy: string): void {
254
- if (process.env.DEBUG_ULTRA_LINTER === 'true') {
255
- console.debug(`[UltraToolCallLinter] recovery ${event}: strategy=${strategy}, source=${candidate.source}`)
256
- }
257
- }
258
- }