generate method
Future<GenerateResult>
generate(
- GenerateTurnState envelope,
- ActionFnArg<
ModelResponseChunk, GenerateActionOptions, void> ctx, - Future<
GenerateResult> next(- GenerateTurnState envelope,
- ActionFnArg<
ModelResponseChunk, GenerateActionOptions, void> ctx
Middleware for the top-level generate call.
Wraps the entire generation process, including the tool loop.
next is the function to call to proceed with the generation.
Implementation
@override
Future<GenerateResult> generate(
GenerateTurnState envelope,
ActionFnArg<ModelResponseChunk, GenerateActionOptions, void> ctx,
Future<GenerateResult> Function(
GenerateTurnState envelope,
ActionFnArg<ModelResponseChunk, GenerateActionOptions, void> ctx,
)
next,
) async {
final options = envelope.request;
// Capture the latest messages for optional history forwarding. Note:
// _delegationCount is NOT reset here — the hook runs on every tool-loop
// turn, but the count must accumulate across the entire generate() call.
_conversationMessages = options.messages;
final agentList = <String>[];
// Whether any sub-agent can leave a continuable handle (server-managed).
// The continue tool is registered unconditionally (the sync `tools` getter
// cannot resolve handles), but this async hook can, so the continue
// guidance is gated on a real capability instead of always shown. Unknown
// metadata counts as continuable (the safe default, matching isClientManaged).
var anyContinuable = false;
for (final agentName in _agentNames) {
final description = await _discoverDescription(agentName);
agentList.add(' - ${_makeToolName(_prefix, agentName)}: $description');
final handle = await _resolveHandle(agentName);
if (handle != null && !handle.isClientManaged) anyContinuable = true;
}
final agentsInstructions = _buildInstructions(agentList, anyContinuable);
final messages = List<Message>.from(options.messages);
// Check if we've already injected (multi-turn).
final alreadyInjected = messages.any(
(msg) => msg.content.any(
(part) => part.isText && part.metadata?[_markerKey] == true,
),
);
if (!alreadyInjected) {
final systemIdx = messages.indexWhere((m) => m.role == Role.system);
if (systemIdx != -1) {
final systemMsg = messages[systemIdx];
messages[systemIdx] = Message(
role: systemMsg.role,
content: [
...systemMsg.content,
TextPart(text: agentsInstructions, metadata: {_markerKey: true}),
],
metadata: systemMsg.metadata,
);
} else {
messages.insert(
0,
Message(
role: Role.system,
content: [
TextPart(text: agentsInstructions, metadata: {_markerKey: true}),
],
),
);
}
}
final newOptions = GenerateActionOptions(
model: options.model,
docs: options.docs,
messages: messages,
tools: options.tools,
toolChoice: options.toolChoice,
config: options.config,
output: options.output,
resume: options.resume,
returnToolRequests: options.returnToolRequests,
maxTurns: options.maxTurns,
stepName: options.stepName,
);
return next(envelope.copyWith(request: newOptions), ctx);
}