@@ -178,7 +178,12 @@ extension Agent {
178178 }
179179
180180 if let tokenBudget = options. tokenBudget, totalUsage. total > tokenBudget {
181- throw AgentError . tokenBudgetExceeded ( budget: tokenBudget, used: totalUsage. total)
181+ return makeTerminalResult (
182+ reason: . tokenBudgetExceeded( budget: tokenBudget, used: totalUsage. total) ,
183+ tokenUsage: totalUsage,
184+ iterations: iteration,
185+ history: messages
186+ )
182187 }
183188
184189 let pruneCalls = response. toolCalls. filter { $0. name == " prune_context " }
@@ -194,7 +199,12 @@ extension Agent {
194199 }
195200 }
196201
197- throw AgentError . maxIterationsReached ( iterations: configuration. maxIterations)
202+ return makeTerminalResult (
203+ reason: . maxIterationsReached( limit: configuration. maxIterations) ,
204+ tokenUsage: totalUsage,
205+ iterations: configuration. maxIterations,
206+ history: messages
207+ )
198208 }
199209
200210 func stream(
@@ -216,7 +226,7 @@ extension Agent {
216226 }
217227}
218228
219- private extension Agent {
229+ extension Agent {
220230 func stream(
221231 userMessage: ChatMessage ,
222232 history: [ ChatMessage ] ,
@@ -258,17 +268,14 @@ private extension Agent {
258268 continuation: AsyncThrowingStream < StreamEvent , Error > . Continuation
259269 ) async throws {
260270 var messages = buildInitialMessages (
261- userMessage: userMessage, history: history,
262- systemPromptOverride: options. systemPromptOverride
271+ userMessage: userMessage, history: history, systemPromptOverride: options. systemPromptOverride
263272 )
264273 var totalUsage = TokenUsage ( )
265274 var lastTotalTokens : Int ?
266275 var sessionAllowlist : Set < String > = [ ]
267276 let policy = StreamPolicy . agent
268277 let processor = StreamProcessor ( client: client, toolDefinitions: toolDefinitions, policy: policy)
269- var compactor = ContextCompactor (
270- client: client, toolDefinitions: toolDefinitions, configuration: configuration
271- )
278+ var compactor = ContextCompactor ( client: client, toolDefinitions: toolDefinitions, configuration: configuration)
272279 var budgetPhase = try makeBudgetPhase ( )
273280
274281 for iterationNumber in 1 ... configuration. maxIterations {
@@ -277,63 +284,57 @@ private extension Agent {
277284 let compacted = await compactor. compactOrTruncateIfNeeded (
278285 & messages, lastTotalTokens: lastTotalTokens, totalUsage: & totalUsage
279286 )
280- if compacted, let totalTokens = lastTotalTokens, let windowSize = client. contextWindowSize {
281- continuation. yield ( . make( . compacted( totalTokens: totalTokens, windowSize: windowSize) ) )
282- }
287+ emitCompactionEventIfNeeded ( compacted, lastTotalTokens: lastTotalTokens, continuation: continuation)
283288 let iteration = try await processor. process (
284- messages: messages,
285- totalUsage: & totalUsage,
286- continuation: continuation,
289+ messages: messages, totalUsage: & totalUsage, continuation: continuation,
287290 requestContext: options. requestContext
288291 )
289292
290293 if let usage = iteration. usage {
291- lastTotalTokens = usage. total
292294 continuation. yield ( . make( . iterationCompleted( usage: usage, iteration: iterationNumber) ) )
293295 }
294-
295296 messages. append ( . assistant( iteration. toAssistantMessage ( ) ) )
296297
297- let filteredTools = policy . executableToolCalls ( from: iteration. toolCalls)
298+ let filteredTools = StreamPolicy . agent . executableToolCalls ( from: iteration. toolCalls)
298299 let pruneCalls = filteredTools. filter { $0. name == " prune_context " }
299300 let regularCalls = filteredTools. filter { $0. name != " prune_context " }
300- let shouldTerminate = policy. shouldTerminateAfterIteration ( toolCalls: iteration. toolCalls)
301301 let budgetUsage = try requireBudgetUsage ( iteration. usage, budgetPhase: budgetPhase)
302302
303303 if let budgetUsage {
304- applyBudgetPhase (
305- & budgetPhase,
306- usage: budgetUsage,
307- messages: & messages,
308- continuation: continuation
309- )
304+ applyBudgetPhase ( & budgetPhase, usage: budgetUsage, messages: & messages, continuation: continuation)
310305 }
311306
312- if shouldTerminate {
313- let finishEvent = try parseFinishEvent (
314- from: iteration. toolCalls, tokenUsage: totalUsage, history: messages
315- )
316- continuation. yield ( finishEvent)
317- continuation. finish ( )
307+ if try finishIfTerminated ( iteration, usage: totalUsage, history: messages, continuation: continuation) {
318308 return
319309 }
320310
321311 executePruneCalls ( pruneCalls, messages: & messages, continuation: continuation)
322312 try await executeStreamingAndAppendResults (
323- regularCalls, context: context, messages: & messages,
313+ regularCalls,
314+ context: context,
315+ messages: & messages,
324316 continuation: continuation,
325- approvalHandler: options. approvalHandler, allowlist: & sessionAllowlist
317+ approvalHandler: options. approvalHandler,
318+ allowlist: & sessionAllowlist
326319 )
327320
328- if let tokenBudget = options. tokenBudget, totalUsage. total > tokenBudget {
329- continuation. finish (
330- throwing: AgentError . tokenBudgetExceeded ( budget: tokenBudget, used: totalUsage. total)
331- )
321+ if finishIfOverBudget (
322+ options. tokenBudget, totalUsage: totalUsage, history: messages, continuation: continuation
323+ ) {
332324 return
333325 }
326+ lastTotalTokens = iteration. usage? . total
334327 }
335328
336- continuation. finish ( throwing: AgentError . maxIterationsReached ( iterations: configuration. maxIterations) )
329+ finishStreaming (
330+ continuation: continuation,
331+ event: makeFinishedEvent (
332+ tokenUsage: totalUsage,
333+ content: nil ,
334+ reason: . maxIterationsReached( limit: configuration. maxIterations) ,
335+ history: messages
336+ )
337+ )
337338 }
338339
339340 func buildInitialMessages(
@@ -354,20 +355,25 @@ private extension Agent {
354355 from toolCalls: [ ToolCall ] , tokenUsage: TokenUsage , history: [ ChatMessage ]
355356 ) throws -> StreamEvent {
356357 guard let finishCall = toolCalls. first ( where: { $0. name == " finish " } ) else {
357- return . make( . finished( tokenUsage: tokenUsage, content: nil , reason: nil , history: history) )
358+ return makeFinishedEvent (
359+ tokenUsage: tokenUsage,
360+ content: nil ,
361+ reason: nil ,
362+ history: history
363+ )
358364 }
359365 let decoded : FinishArguments
360366 do {
361367 decoded = try JSONDecoder ( ) . decode ( FinishArguments . self, from: finishCall. argumentsData)
362368 } catch {
363369 throw AgentError . finishDecodingFailed ( message: String ( describing: error) )
364370 }
365- return . make ( . finished (
371+ return makeFinishedEvent (
366372 tokenUsage: tokenUsage,
367373 content: decoded. content,
368374 reason: FinishReason ( decoded. reason ?? " completed " ) ,
369375 history: history
370- ) )
376+ )
371377 }
372378
373379 func parseFinishResult(
@@ -391,4 +397,89 @@ private extension Agent {
391397 history: history
392398 )
393399 }
400+
401+ private func makeFinishedEvent(
402+ tokenUsage: TokenUsage ,
403+ content: String ? ,
404+ reason: FinishReason ? ,
405+ history: [ ChatMessage ]
406+ ) -> StreamEvent {
407+ . make( . finished(
408+ tokenUsage: tokenUsage,
409+ content: content,
410+ reason: reason,
411+ history: history
412+ ) )
413+ }
414+
415+ private func makeTerminalResult(
416+ reason: FinishReason ,
417+ tokenUsage: TokenUsage ,
418+ iterations: Int ,
419+ history: [ ChatMessage ]
420+ ) -> AgentResult {
421+ AgentResult (
422+ finishReason: reason,
423+ content: nil ,
424+ totalTokenUsage: tokenUsage,
425+ iterations: iterations,
426+ history: history
427+ )
428+ }
429+
430+ private func emitCompactionEventIfNeeded(
431+ _ compacted: Bool ,
432+ lastTotalTokens: Int ? ,
433+ continuation: AsyncThrowingStream < StreamEvent , Error > . Continuation
434+ ) {
435+ guard compacted, let totalTokens = lastTotalTokens, let windowSize = client. contextWindowSize else {
436+ return
437+ }
438+ continuation. yield ( . make( . compacted( totalTokens: totalTokens, windowSize: windowSize) ) )
439+ }
440+
441+ private func finishIfTerminated(
442+ _ iteration: StreamIteration ,
443+ usage: TokenUsage ,
444+ history: [ ChatMessage ] ,
445+ continuation: AsyncThrowingStream < StreamEvent , Error > . Continuation
446+ ) throws -> Bool {
447+ guard StreamPolicy . agent. shouldTerminateAfterIteration ( toolCalls: iteration. toolCalls) else {
448+ return false
449+ }
450+ try finishStreaming (
451+ continuation: continuation,
452+ event: parseFinishEvent ( from: iteration. toolCalls, tokenUsage: usage, history: history)
453+ )
454+ return true
455+ }
456+
457+ private func finishIfOverBudget(
458+ _ tokenBudget: Int ? ,
459+ totalUsage: TokenUsage ,
460+ history: [ ChatMessage ] ,
461+ continuation: AsyncThrowingStream < StreamEvent , Error > . Continuation
462+ ) -> Bool {
463+ guard let tokenBudget, totalUsage. total > tokenBudget else {
464+ return false
465+ }
466+ finishStreaming (
467+ continuation: continuation,
468+ event: makeFinishedEvent (
469+ tokenUsage: totalUsage,
470+ content: nil ,
471+ reason: . tokenBudgetExceeded( budget: tokenBudget, used: totalUsage. total) ,
472+ history: history
473+ )
474+ )
475+ return true
476+ }
477+
478+ private func finishStreaming(
479+ continuation: AsyncThrowingStream < StreamEvent , Error > . Continuation ,
480+ event: StreamEvent
481+ ) {
482+ continuation. yield ( event)
483+ continuation. finish ( )
484+ }
394485}
0 commit comments