mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
Merge branch 'main' into fix/subagent-update
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { SubagentConfig } from './types.js';
|
||||
import type { SubagentConfig } from './types.js';
|
||||
|
||||
/**
|
||||
* Registry of built-in subagents that are always available to all users.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import {
|
||||
import type {
|
||||
ToolCallConfirmationDetails,
|
||||
ToolConfirmationOutcome,
|
||||
} from '../tools/tools.js';
|
||||
|
||||
@@ -9,9 +9,9 @@ import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { SubagentManager } from './subagent-manager.js';
|
||||
import { SubagentConfig, SubagentError } from './types.js';
|
||||
import { ToolRegistry } from '../tools/tool-registry.js';
|
||||
import { Config } from '../config/config.js';
|
||||
import { type SubagentConfig, SubagentError } from './types.js';
|
||||
import type { ToolRegistry } from '../tools/tool-registry.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { makeFakeConfig } from '../test-utils/config.js';
|
||||
|
||||
// Mock file system operations
|
||||
|
||||
@@ -13,22 +13,21 @@ import {
|
||||
parse as parseYaml,
|
||||
stringify as stringifyYaml,
|
||||
} from '../utils/yaml-parser.js';
|
||||
import {
|
||||
import type {
|
||||
SubagentConfig,
|
||||
SubagentRuntimeConfig,
|
||||
SubagentLevel,
|
||||
ListSubagentsOptions,
|
||||
CreateSubagentOptions,
|
||||
SubagentError,
|
||||
SubagentErrorCode,
|
||||
PromptConfig,
|
||||
ModelConfig,
|
||||
RunConfig,
|
||||
ToolConfig,
|
||||
} from './types.js';
|
||||
import { SubagentError, SubagentErrorCode } from './types.js';
|
||||
import { SubagentValidator } from './validation.js';
|
||||
import { SubAgentScope } from './subagent.js';
|
||||
import { Config } from '../config/config.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { BuiltinAgentRegistry } from './builtin-agents.js';
|
||||
|
||||
const QWEN_CONFIG_DIR = '.qwen';
|
||||
|
||||
@@ -4,31 +4,39 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { vi, describe, it, expect, beforeEach, Mock, afterEach } from 'vitest';
|
||||
import { ContextState, SubAgentScope } from './subagent.js';
|
||||
import {
|
||||
SubagentTerminateMode,
|
||||
PromptConfig,
|
||||
ModelConfig,
|
||||
RunConfig,
|
||||
ToolConfig,
|
||||
} from './types.js';
|
||||
import { Config, ConfigParameters } from '../config/config.js';
|
||||
import { GeminiChat } from '../core/geminiChat.js';
|
||||
import { createContentGenerator } from '../core/contentGenerator.js';
|
||||
import { getEnvironmentContext } from '../utils/environmentContext.js';
|
||||
import { executeToolCall } from '../core/nonInteractiveToolExecutor.js';
|
||||
import { ToolRegistry } from '../tools/tool-registry.js';
|
||||
import { AnyDeclarativeTool } from '../tools/tools.js';
|
||||
import { DEFAULT_GEMINI_MODEL } from '../config/models.js';
|
||||
import {
|
||||
import type {
|
||||
Content,
|
||||
FunctionCall,
|
||||
FunctionDeclaration,
|
||||
GenerateContentConfig,
|
||||
Part,
|
||||
Type,
|
||||
} from '@google/genai';
|
||||
import { Type } from '@google/genai';
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { Config, type ConfigParameters } from '../config/config.js';
|
||||
import { DEFAULT_GEMINI_MODEL } from '../config/models.js';
|
||||
import { createContentGenerator } from '../core/contentGenerator.js';
|
||||
import { GeminiChat } from '../core/geminiChat.js';
|
||||
import { executeToolCall } from '../core/nonInteractiveToolExecutor.js';
|
||||
import type { ToolRegistry } from '../tools/tool-registry.js';
|
||||
import { type AnyDeclarativeTool } from '../tools/tools.js';
|
||||
import { getEnvironmentContext } from '../utils/environmentContext.js';
|
||||
import { ContextState, SubAgentScope } from './subagent.js';
|
||||
import type {
|
||||
ModelConfig,
|
||||
PromptConfig,
|
||||
RunConfig,
|
||||
ToolConfig,
|
||||
} from './types.js';
|
||||
import { SubagentTerminateMode } from './types.js';
|
||||
|
||||
vi.mock('../core/geminiChat.js');
|
||||
vi.mock('../core/contentGenerator.js');
|
||||
@@ -56,6 +64,7 @@ async function createMockConfig(
|
||||
getTool: vi.fn(),
|
||||
getFunctionDeclarations: vi.fn().mockReturnValue([]),
|
||||
getFunctionDeclarationsFiltered: vi.fn().mockReturnValue([]),
|
||||
getAllToolNames: vi.fn().mockReturnValue([]),
|
||||
...toolRegistryMocks,
|
||||
} as unknown as ToolRegistry;
|
||||
|
||||
@@ -68,32 +77,46 @@ const createMockStream = (
|
||||
functionCallsList: Array<FunctionCall[] | 'stop'>,
|
||||
) => {
|
||||
let index = 0;
|
||||
return vi.fn().mockImplementation(() => {
|
||||
// This mock now returns a Promise that resolves to the async generator,
|
||||
// matching the new signature for sendMessageStream.
|
||||
return vi.fn().mockImplementation(async () => {
|
||||
const response = functionCallsList[index] || 'stop';
|
||||
index++;
|
||||
|
||||
return (async function* () {
|
||||
if (response === 'stop') {
|
||||
// When stopping, the model might return text, but the subagent logic primarily cares about the absence of functionCalls.
|
||||
yield {
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
parts: [{ text: 'Done.' }],
|
||||
type: 'chunk',
|
||||
value: {
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
parts: [{ text: 'Done.' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
} else if (response.length > 0) {
|
||||
yield { functionCalls: response };
|
||||
yield {
|
||||
type: 'chunk',
|
||||
value: {
|
||||
functionCalls: response,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
yield {
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
parts: [{ text: 'Done.' }],
|
||||
type: 'chunk',
|
||||
value: {
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
parts: [{ text: 'Done.' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}; // Handle empty array also as stop
|
||||
}
|
||||
})();
|
||||
@@ -154,7 +177,7 @@ describe('subagent.ts', () => {
|
||||
// Default mock for executeToolCall
|
||||
vi.mocked(executeToolCall).mockResolvedValue({
|
||||
callId: 'default-call',
|
||||
responseParts: 'default response',
|
||||
responseParts: [{ text: 'default response' }],
|
||||
resultDisplay: 'Default tool result',
|
||||
error: undefined,
|
||||
errorType: undefined,
|
||||
@@ -196,7 +219,8 @@ describe('subagent.ts', () => {
|
||||
|
||||
it('should not block creation when a tool may require confirmation', async () => {
|
||||
const mockTool = {
|
||||
schema: { parameters: { type: Type.OBJECT, properties: {} } },
|
||||
name: 'risky_tool',
|
||||
schema: { parametersJsonSchema: { type: 'object', properties: {} } },
|
||||
build: vi.fn().mockReturnValue({
|
||||
shouldConfirmExecute: vi.fn().mockResolvedValue({
|
||||
type: 'exec',
|
||||
@@ -226,7 +250,8 @@ describe('subagent.ts', () => {
|
||||
|
||||
it('should succeed if tools do not require confirmation', async () => {
|
||||
const mockTool = {
|
||||
schema: { parameters: { type: Type.OBJECT, properties: {} } },
|
||||
name: 'safe_tool',
|
||||
schema: { parametersJsonSchema: { type: 'object', properties: {} } },
|
||||
build: vi.fn().mockReturnValue({
|
||||
shouldConfirmExecute: vi.fn().mockResolvedValue(null),
|
||||
}),
|
||||
@@ -251,11 +276,12 @@ describe('subagent.ts', () => {
|
||||
|
||||
it('should allow creation regardless of tool parameter requirements', async () => {
|
||||
const mockToolWithParams = {
|
||||
name: 'tool_with_params',
|
||||
schema: {
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
path: { type: Type.STRING },
|
||||
path: { type: 'string' },
|
||||
},
|
||||
required: ['path'],
|
||||
},
|
||||
@@ -265,6 +291,7 @@ describe('subagent.ts', () => {
|
||||
|
||||
const { config } = await createMockConfig({
|
||||
getTool: vi.fn().mockReturnValue(mockToolWithParams),
|
||||
getAllTools: vi.fn().mockReturnValue([mockToolWithParams]),
|
||||
});
|
||||
|
||||
const toolConfig: ToolConfig = { tools: ['tool_with_params'] };
|
||||
@@ -480,6 +507,7 @@ describe('subagent.ts', () => {
|
||||
getFunctionDeclarationsFiltered: vi
|
||||
.fn()
|
||||
.mockReturnValue([listFilesToolDef]),
|
||||
getTool: vi.fn().mockReturnValue(undefined),
|
||||
});
|
||||
const toolConfig: ToolConfig = { tools: ['list_files'] };
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
*/
|
||||
|
||||
import { reportError } from '../utils/errorReporting.js';
|
||||
import { Config } from '../config/config.js';
|
||||
import { ToolCallRequestInfo } from '../core/turn.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { type ToolCallRequestInfo } from '../core/turn.js';
|
||||
import {
|
||||
CoreToolScheduler,
|
||||
ToolCall,
|
||||
WaitingToolCall,
|
||||
type ToolCall,
|
||||
type WaitingToolCall,
|
||||
} from '../core/coreToolScheduler.js';
|
||||
import type {
|
||||
ToolConfirmationOutcome,
|
||||
@@ -18,7 +18,7 @@ import type {
|
||||
} from '../tools/tools.js';
|
||||
import { createContentGenerator } from '../core/contentGenerator.js';
|
||||
import { getEnvironmentContext } from '../utils/environmentContext.js';
|
||||
import {
|
||||
import type {
|
||||
Content,
|
||||
Part,
|
||||
FunctionCall,
|
||||
@@ -27,16 +27,14 @@ import {
|
||||
GenerateContentResponseUsageMetadata,
|
||||
} from '@google/genai';
|
||||
import { GeminiChat } from '../core/geminiChat.js';
|
||||
import {
|
||||
SubagentTerminateMode,
|
||||
import type {
|
||||
PromptConfig,
|
||||
ModelConfig,
|
||||
RunConfig,
|
||||
ToolConfig,
|
||||
} from './types.js';
|
||||
import {
|
||||
SubAgentEventEmitter,
|
||||
SubAgentEventType,
|
||||
import { SubagentTerminateMode } from './types.js';
|
||||
import type {
|
||||
SubAgentFinishEvent,
|
||||
SubAgentRoundEvent,
|
||||
SubAgentStartEvent,
|
||||
@@ -45,11 +43,15 @@ import {
|
||||
SubAgentStreamTextEvent,
|
||||
SubAgentErrorEvent,
|
||||
} from './subagent-events.js';
|
||||
import {
|
||||
type SubAgentEventEmitter,
|
||||
SubAgentEventType,
|
||||
} from './subagent-events.js';
|
||||
import {
|
||||
SubagentStatistics,
|
||||
SubagentStatsSummary,
|
||||
type SubagentStatsSummary,
|
||||
} from './subagent-statistics.js';
|
||||
import { SubagentHooks } from './subagent-hooks.js';
|
||||
import type { SubagentHooks } from './subagent-hooks.js';
|
||||
import { logSubagentExecution } from '../telemetry/loggers.js';
|
||||
import { SubagentExecutionEvent } from '../telemetry/types.js';
|
||||
import { TaskTool } from '../tools/task.js';
|
||||
@@ -379,26 +381,36 @@ export class SubAgentScope {
|
||||
let roundText = '';
|
||||
let lastUsage: GenerateContentResponseUsageMetadata | undefined =
|
||||
undefined;
|
||||
for await (const resp of responseStream) {
|
||||
for await (const streamEvent of responseStream) {
|
||||
if (abortController.signal.aborted) {
|
||||
this.terminateMode = SubagentTerminateMode.CANCELLED;
|
||||
return;
|
||||
}
|
||||
if (resp.functionCalls) functionCalls.push(...resp.functionCalls);
|
||||
const content = resp.candidates?.[0]?.content;
|
||||
const parts = content?.parts || [];
|
||||
for (const p of parts) {
|
||||
const txt = (p as Part & { text?: string }).text;
|
||||
if (txt) roundText += txt;
|
||||
if (txt)
|
||||
this.eventEmitter?.emit(SubAgentEventType.STREAM_TEXT, {
|
||||
subagentId: this.subagentId,
|
||||
round: turnCounter,
|
||||
text: txt,
|
||||
timestamp: Date.now(),
|
||||
} as SubAgentStreamTextEvent);
|
||||
|
||||
// Handle retry events
|
||||
if (streamEvent.type === 'retry') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle chunk events
|
||||
if (streamEvent.type === 'chunk') {
|
||||
const resp = streamEvent.value;
|
||||
if (resp.functionCalls) functionCalls.push(...resp.functionCalls);
|
||||
const content = resp.candidates?.[0]?.content;
|
||||
const parts = content?.parts || [];
|
||||
for (const p of parts) {
|
||||
const txt = (p as Part & { text?: string }).text;
|
||||
if (txt) roundText += txt;
|
||||
if (txt)
|
||||
this.eventEmitter?.emit(SubAgentEventType.STREAM_TEXT, {
|
||||
subagentId: this.subagentId,
|
||||
round: turnCounter,
|
||||
text: txt,
|
||||
timestamp: Date.now(),
|
||||
} as SubAgentStreamTextEvent);
|
||||
}
|
||||
if (resp.usageMetadata) lastUsage = resp.usageMetadata;
|
||||
}
|
||||
if (resp.usageMetadata) lastUsage = resp.usageMetadata;
|
||||
}
|
||||
this.executionStats.rounds = turnCounter;
|
||||
this.stats.setRounds(turnCounter);
|
||||
@@ -546,7 +558,6 @@ export class SubAgentScope {
|
||||
const responded = new Set<string>();
|
||||
let resolveBatch: (() => void) | null = null;
|
||||
const scheduler = new CoreToolScheduler({
|
||||
toolRegistry: this.runtimeContext.getToolRegistry(),
|
||||
outputUpdateHandler: undefined,
|
||||
onAllToolCallsComplete: async (completedCalls) => {
|
||||
for (const call of completedCalls) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Content, FunctionDeclaration } from '@google/genai';
|
||||
import type { Content, FunctionDeclaration } from '@google/genai';
|
||||
|
||||
/**
|
||||
* Represents the storage level for a subagent configuration.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { SubagentValidator } from './validation.js';
|
||||
import { SubagentConfig, SubagentError } from './types.js';
|
||||
import { type SubagentConfig, SubagentError } from './types.js';
|
||||
|
||||
describe('SubagentValidator', () => {
|
||||
let validator: SubagentValidator;
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
SubagentConfig,
|
||||
ValidationResult,
|
||||
SubagentError,
|
||||
SubagentErrorCode,
|
||||
import { SubagentError, SubagentErrorCode } from './types.js';
|
||||
import type {
|
||||
ModelConfig,
|
||||
RunConfig,
|
||||
SubagentConfig,
|
||||
ValidationResult,
|
||||
} from './types.js';
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user