@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,236 +0,0 @@
1
- /*
2
- * File: executor.ts
3
- * Project: qwenproxy
4
- * Execution loop for tool calling - agentic loop that handles
5
- * send -> tool calls -> execute -> re-send until completion
6
- */
7
-
8
- import { v4 as uuidv4 } from 'uuid';
9
- import type { ParsedToolCall, ToolCallResult, ToolContext } from './types';
10
- import { SchemaValidationError } from './schema.js';
11
- import { registry } from './registry';
12
- import { robustParseJSON } from '../utils/json.js';
13
-
14
- export interface ExecutionLoopConfig {
15
- maxTurns?: number;
16
- debug?: boolean;
17
- }
18
-
19
- export interface LoopTurnResult {
20
- toolCalls: ParsedToolCall[];
21
- toolResults: ToolCallResult[];
22
- content: string | null;
23
- finishReason: string | null;
24
- turn: number;
25
- }
26
-
27
- export type LLMSendFunction = (
28
- messages: unknown[],
29
- tools: unknown[] | undefined,
30
- model: string
31
- ) => Promise<LLMResponse>;
32
-
33
- export interface LLMResponse {
34
- content: string | null;
35
- toolCalls: ParsedToolCall[];
36
- finishReason: string;
37
- }
38
-
39
- const TOOL_START_TAG = '<' + 'tool_call>';
40
- const TOOL_END_TAG = '</' + 'tool_call>';
41
-
42
- export function parseToolCallsFromContent(content: string): {
43
- textContent: string;
44
- toolCalls: ParsedToolCall[];
45
- } {
46
- const toolCalls: ParsedToolCall[] = [];
47
- let remaining = content;
48
- let textContent = '';
49
-
50
- while (true) {
51
- const startIdx = remaining.indexOf(TOOL_START_TAG);
52
- if (startIdx === -1) {
53
- textContent += remaining;
54
- break;
55
- }
56
-
57
- textContent += remaining.substring(0, startIdx);
58
-
59
- const endIdx = remaining.indexOf(TOOL_END_TAG, startIdx + TOOL_START_TAG.length);
60
- if (endIdx === -1) {
61
- textContent += remaining.substring(startIdx);
62
- break;
63
- }
64
-
65
- const jsonStr = remaining
66
- .substring(startIdx + TOOL_START_TAG.length, endIdx)
67
- .trim();
68
-
69
- try {
70
- const parsed = robustParseJSON(jsonStr);
71
- if (!parsed) throw new Error('Failed to parse JSON');
72
-
73
- toolCalls.push({
74
- id: 'call_' + uuidv4(),
75
- name: parsed.name || '',
76
- arguments: parsed.arguments
77
- ? (typeof parsed.arguments === 'string' ? JSON.parse(parsed.arguments) : parsed.arguments)
78
- : (() => {
79
- const { name, ...rest } = parsed;
80
- return rest;
81
- })(),
82
- });
83
- } catch (e) {
84
- textContent += TOOL_START_TAG + jsonStr + TOOL_END_TAG;
85
- }
86
-
87
- remaining = remaining.substring(endIdx + TOOL_END_TAG.length);
88
- }
89
-
90
- return { textContent: textContent.trim(), toolCalls };
91
- }
92
-
93
- export async function executeToolCalls(
94
- toolCalls: ParsedToolCall[],
95
- context: ToolContext
96
- ): Promise<ToolCallResult[]> {
97
- return await Promise.all(
98
- toolCalls.map(async (tc) => {
99
- try {
100
- if (!registry.has(tc.name)) {
101
- return {
102
- toolCallId: tc.id,
103
- name: tc.name,
104
- result: JSON.stringify({ error: `Unknown tool: '${tc.name}'` }),
105
- isError: true,
106
- };
107
- }
108
-
109
- const result = await registry.execute(tc.name, tc.arguments, context);
110
- return {
111
- toolCallId: tc.id,
112
- name: tc.name,
113
- result,
114
- isError: false,
115
- };
116
- } catch (err: unknown) {
117
- const message = err instanceof Error ? err.message : String(err);
118
- const isValidation = err instanceof SchemaValidationError;
119
- return {
120
- toolCallId: tc.id,
121
- name: tc.name,
122
- result: JSON.stringify({
123
- error: isValidation ? 'Schema validation failed' : 'Tool execution error',
124
- details: message,
125
- ...(isValidation ? { path: (err as SchemaValidationError).path } : {}),
126
- }),
127
- isError: true,
128
- };
129
- }
130
- })
131
- );
132
- }
133
-
134
- function buildToolMessage(result: ToolCallResult): Record<string, unknown> {
135
- return {
136
- role: 'tool',
137
- tool_call_id: result.toolCallId,
138
- content: result.result,
139
- };
140
- }
141
-
142
- function buildAssistantToolCallMessage(
143
- content: string | null,
144
- toolCalls: ParsedToolCall[]
145
- ): Record<string, unknown> {
146
- return {
147
- role: 'assistant',
148
- content: content || null,
149
- tool_calls: toolCalls.map((tc) => ({
150
- id: tc.id,
151
- type: 'function',
152
- function: {
153
- name: tc.name,
154
- arguments: typeof tc.arguments === 'string'
155
- ? tc.arguments
156
- : JSON.stringify(tc.arguments),
157
- },
158
- })),
159
- };
160
- }
161
-
162
- export async function runExecutionLoop(
163
- sendToLLM: LLMSendFunction,
164
- messages: unknown[],
165
- model: string,
166
- config: ExecutionLoopConfig = {}
167
- ): Promise<string> {
168
- const maxTurns = config.maxTurns ?? 10;
169
- const debug = config.debug ?? false;
170
-
171
- const tools = registry.listNames().length > 0
172
- ? registry.toOpenAITools()
173
- : undefined;
174
-
175
- for (let turn = 0; turn < maxTurns; turn++) {
176
- if (debug) {
177
- console.log(`[executor] Turn ${turn + 1}/${maxTurns}, messages: ${messages.length}`);
178
- }
179
-
180
- const response = await sendToLLM(messages, tools, model);
181
-
182
- const hasStructuredToolCalls = response.toolCalls && response.toolCalls.length > 0;
183
- let parsedFromContent: { textContent: string; toolCalls: ParsedToolCall[] } | null = null;
184
-
185
- if (!hasStructuredToolCalls && response.content) {
186
- parsedFromContent = parseToolCallsFromContent(response.content);
187
- }
188
-
189
- const effectiveToolCalls = hasStructuredToolCalls
190
- ? response.toolCalls
191
- : parsedFromContent?.toolCalls || [];
192
-
193
- const effectiveContent = parsedFromContent
194
- ? parsedFromContent.textContent
195
- : response.content;
196
-
197
- if (effectiveToolCalls.length === 0) {
198
- if (debug) {
199
- console.log('[executor] No tool calls, loop complete');
200
- }
201
- return effectiveContent || '';
202
- }
203
-
204
- const context: ToolContext = {
205
- messages,
206
- turn,
207
- model,
208
- };
209
-
210
- if (debug) {
211
- console.log(
212
- `[executor] Executing ${effectiveToolCalls.length} tool calls:`,
213
- effectiveToolCalls.map((tc) => tc.name)
214
- );
215
- }
216
-
217
- const toolResults = await executeToolCalls(effectiveToolCalls, context);
218
-
219
- messages.push(buildAssistantToolCallMessage(effectiveContent, effectiveToolCalls));
220
-
221
- for (const result of toolResults) {
222
- messages.push(buildToolMessage(result));
223
- }
224
-
225
- if (debug) {
226
- console.log(
227
- `[executor] Tool results:`,
228
- toolResults.map((r) => ({ name: r.name, isError: r.isError }))
229
- );
230
- }
231
- }
232
-
233
- throw new Error(
234
- `Execution loop exceeded maximum turns (${maxTurns}). The agent may be stuck in a cycle.`
235
- );
236
- }
@@ -1,122 +0,0 @@
1
- import type { ToolCallAST, ToolDefinition } from './ast.js';
2
- import { detectToolCalls } from './detector.js';
3
- import { repairToolCall } from './repair.js';
4
- import { RequestToolRegistry } from './registry-runtime.js';
5
- import { coerceArguments } from './coercion.js';
6
- import { validateToolCall, type ValidationResult } from './validator.js';
7
- import { calculateConfidence } from './confidence.js';
8
- import { v4 as uuidv4 } from 'uuid';
9
-
10
- export interface PipelineResult {
11
- textContent: string;
12
- toolCalls: ToolCallAST[];
13
- errors: Array<{
14
- toolName?: string;
15
- code: string;
16
- message: string;
17
- details?: any;
18
- }>;
19
- }
20
-
21
- const CONFIDENCE_THRESHOLD = 0.7;
22
-
23
- export function processToolCalls(
24
- text: string,
25
- requestTools: unknown[]
26
- ): PipelineResult {
27
- const registry = new RequestToolRegistry(requestTools);
28
- const detected = detectToolCalls(text);
29
-
30
- const toolCalls: ToolCallAST[] = [];
31
- const errors: PipelineResult['errors'] = [];
32
- let textContent = text;
33
-
34
- for (const det of detected) {
35
- while (textContent.includes(det.raw)) {
36
- textContent = textContent.replace(det.raw, '').trim();
37
- }
38
- }
39
-
40
- for (const det of detected) {
41
- const repaired = repairToolCall(det.extracted);
42
- if (!repaired) {
43
- errors.push({
44
- code: 'MALFORMED_TOOL_CALL',
45
- message: 'Could not repair or identify tool call structure',
46
- details: det.extracted,
47
- });
48
- continue;
49
- }
50
-
51
- if (!registry.has(repaired.name)) {
52
- errors.push({
53
- toolName: repaired.name,
54
- code: 'UNKNOWN_TOOL',
55
- message: `Tool '${repaired.name}' is not registered or provided in the request`,
56
- });
57
- continue;
58
- }
59
-
60
- const toolDef = registry.get(repaired.name)!;
61
- const coercedArgs = coerceArguments(repaired.arguments, toolDef.schema);
62
-
63
- const ast: ToolCallAST = {
64
- id: `call_${uuidv4()}`,
65
- name: repaired.name,
66
- arguments: coercedArgs,
67
- raw: det.raw,
68
- confidence: 0.0,
69
- };
70
-
71
- const validation: ValidationResult = validateToolCall(ast, toolDef);
72
- const confidenceResult = calculateConfidence(ast, toolDef);
73
- ast.confidence = confidenceResult.score;
74
-
75
- if (!validation.valid) {
76
- if (validation.missingFields.length > 0) {
77
- errors.push({
78
- toolName: ast.name,
79
- code: 'MISSING_REQUIRED_FIELD',
80
- message: `Missing required fields: ${validation.missingFields.join(', ')}`,
81
- details: validation.errors,
82
- });
83
- } else {
84
- errors.push({
85
- toolName: ast.name,
86
- code: 'SCHEMA_VALIDATION_FAILED',
87
- message: 'Arguments do not match the tool schema',
88
- details: validation.errors,
89
- });
90
- }
91
- continue;
92
- }
93
-
94
- if (ast.confidence >= CONFIDENCE_THRESHOLD) {
95
- toolCalls.push(ast);
96
- } else {
97
- errors.push({
98
- toolName: ast.name,
99
- code: 'LOW_CONFIDENCE',
100
- message: `Tool call confidence ${ast.confidence} is below threshold ${CONFIDENCE_THRESHOLD}`,
101
- details: confidenceResult.reasons,
102
- });
103
- }
104
- }
105
-
106
- return {
107
- textContent,
108
- toolCalls,
109
- errors,
110
- };
111
- }
112
-
113
- export function formatOpenAIToolCalls(toolCalls: ToolCallAST[]): any[] {
114
- return toolCalls.map(tc => ({
115
- id: tc.id,
116
- type: 'function',
117
- function: {
118
- name: tc.name,
119
- arguments: JSON.stringify(tc.arguments),
120
- },
121
- }));
122
- }
@@ -1,34 +0,0 @@
1
- import type { ToolDefinition } from './ast.js';
2
-
3
- export class RequestToolRegistry {
4
- private registry: Map<string, ToolDefinition>;
5
-
6
- constructor(requestTools: unknown[] = []) {
7
- this.registry = new Map();
8
- for (const tool of requestTools) {
9
- if (tool && typeof tool === 'object') {
10
- const t = tool as any;
11
- if (t.type === 'function' && t.function && typeof t.function === 'object') {
12
- const fn = t.function;
13
- this.registry.set(fn.name, {
14
- name: fn.name,
15
- description: fn.description,
16
- schema: fn.parameters || {},
17
- });
18
- }
19
- }
20
- }
21
- }
22
-
23
- has(name: string): boolean {
24
- return this.registry.has(name);
25
- }
26
-
27
- get(name: string): ToolDefinition | undefined {
28
- return this.registry.get(name);
29
- }
30
-
31
- list(): ToolDefinition[] {
32
- return Array.from(this.registry.values());
33
- }
34
- }
@@ -1,42 +0,0 @@
1
- export interface RepairedTool {
2
- name: string;
3
- arguments: unknown;
4
- }
5
-
6
- export function repairToolCall(extracted: Record<string, unknown>): RepairedTool | null {
7
- if ('tool' in extracted && !('name' in extracted)) {
8
- return {
9
- name: String(extracted.tool),
10
- arguments: extracted.arguments || {},
11
- };
12
- }
13
-
14
- if ('function' in extracted && typeof extracted.function === 'object' && extracted.function !== null) {
15
- const fn = extracted.function as Record<string, unknown>;
16
- if ('name' in fn) {
17
- return {
18
- name: String(fn.name),
19
- arguments: fn.arguments || {},
20
- };
21
- }
22
- }
23
-
24
- if ('function_call' in extracted && typeof extracted.function_call === 'object' && extracted.function_call !== null) {
25
- const fn = extracted.function_call as Record<string, unknown>;
26
- if ('name' in fn) {
27
- return {
28
- name: String(fn.name),
29
- arguments: fn.arguments || {},
30
- };
31
- }
32
- }
33
-
34
- if ('name' in extracted) {
35
- return {
36
- name: String(extracted.name),
37
- arguments: extracted.arguments || {},
38
- };
39
- }
40
-
41
- return null;
42
- }
@@ -1,33 +0,0 @@
1
- import Ajv from 'ajv';
2
- import type { ToolCallAST, ToolDefinition } from './ast.js';
3
-
4
- const ajv = new Ajv({ allErrors: true, strict: false });
5
-
6
- export interface ValidationResult {
7
- valid: boolean;
8
- errors: any[] | null;
9
- missingFields: string[];
10
- }
11
-
12
- export function validateToolCall(ast: ToolCallAST, toolDef: ToolDefinition): ValidationResult {
13
- const validate = ajv.compile(toolDef.schema);
14
- const valid = validate(ast.arguments);
15
-
16
- const missingFields: string[] = [];
17
- if (toolDef.schema && typeof toolDef.schema === 'object' && 'required' in toolDef.schema) {
18
- const required = (toolDef.schema as any).required;
19
- if (Array.isArray(required)) {
20
- for (const field of required) {
21
- if (!(ast.arguments && typeof ast.arguments === 'object' && field in (ast.arguments as Record<string, unknown>))) {
22
- missingFields.push(field);
23
- }
24
- }
25
- }
26
- }
27
-
28
- return {
29
- valid,
30
- errors: validate.errors ?? null,
31
- missingFields,
32
- };
33
- }