@wingman-ai/gateway 0.5.0 → 0.5.2

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 (30) hide show
  1. package/dist/cli/core/agentInvoker.cjs +351 -12
  2. package/dist/cli/core/agentInvoker.d.ts +18 -1
  3. package/dist/cli/core/agentInvoker.js +319 -4
  4. package/dist/cli/core/outputManager.cjs +22 -1
  5. package/dist/cli/core/outputManager.d.ts +17 -1
  6. package/dist/cli/core/outputManager.js +22 -1
  7. package/dist/cli/types.d.ts +18 -1
  8. package/dist/cli/ui/App.cjs +2 -0
  9. package/dist/cli/ui/App.js +2 -0
  10. package/dist/gateway/server.cjs +1 -0
  11. package/dist/gateway/server.js +1 -0
  12. package/dist/tests/agentInvokerSummarization.test.cjs +139 -0
  13. package/dist/tests/agentInvokerSummarization.test.js +140 -1
  14. package/dist/tests/agentInvokerTokenUsage.test.cjs +124 -0
  15. package/dist/tests/agentInvokerTokenUsage.test.d.ts +1 -0
  16. package/dist/tests/agentInvokerTokenUsage.test.js +118 -0
  17. package/dist/tests/gateway-http-security.test.cjs +20 -0
  18. package/dist/tests/gateway-http-security.test.js +20 -0
  19. package/dist/tests/integration/summarization-e2e.integration.test.cjs +127 -0
  20. package/dist/tests/integration/summarization-e2e.integration.test.d.ts +1 -0
  21. package/dist/tests/integration/summarization-e2e.integration.test.js +121 -0
  22. package/dist/tests/outputManagerContextSummarized.test.cjs +43 -0
  23. package/dist/tests/outputManagerContextSummarized.test.d.ts +1 -0
  24. package/dist/tests/outputManagerContextSummarized.test.js +37 -0
  25. package/dist/webui/assets/index-D07GBGp0.js +215 -0
  26. package/dist/webui/assets/index-DV8IYeOw.css +11 -0
  27. package/dist/webui/index.html +2 -2
  28. package/package.json +2 -1
  29. package/dist/webui/assets/index-_GQBoNDx.js +0 -215
  30. package/dist/webui/assets/index-tPN3uQMb.css +0 -11
@@ -124,6 +124,13 @@ const configureDeepAgentSummarizationMiddleware = (agent, settings, model)=>{
124
124
  }
125
125
  });
126
126
  };
127
+ const recompileDeepAgentWithMiddlewareOverrides = (agent)=>{
128
+ if (agent && "object" == typeof agent) {
129
+ const maybeWithConfig = agent.withConfig;
130
+ if ("function" == typeof maybeWithConfig) return maybeWithConfig.call(agent, {});
131
+ }
132
+ return agent;
133
+ };
127
134
  const detectToolEventContext = (chunk)=>{
128
135
  if (!chunk || "object" != typeof chunk || Array.isArray(chunk)) return null;
129
136
  const eventChunk = chunk;
@@ -134,6 +141,85 @@ const detectToolEventContext = (chunk)=>{
134
141
  toolName
135
142
  };
136
143
  };
144
+ const chunkHasBuiltInSummarizationSignal = (chunk)=>{
145
+ if (!chunk || "object" != typeof chunk || Array.isArray(chunk)) return false;
146
+ const eventChunk = chunk;
147
+ if ("on_chain_end" !== eventChunk.event || "SummarizationMiddleware.before_model" !== eventChunk.name) return false;
148
+ const data = eventChunk.data && "object" == typeof eventChunk.data && !Array.isArray(eventChunk.data) ? eventChunk.data : null;
149
+ const output = data?.output && "object" == typeof data.output && !Array.isArray(data.output) ? data.output : null;
150
+ const outputMessages = Array.isArray(output?.messages) ? output.messages : [];
151
+ return outputMessages.some((message)=>{
152
+ if (!message || "object" != typeof message || Array.isArray(message)) return false;
153
+ const messageRecord = message;
154
+ const additionalKwargs = messageRecord.additional_kwargs && "object" == typeof messageRecord.additional_kwargs && !Array.isArray(messageRecord.additional_kwargs) ? messageRecord.additional_kwargs : null;
155
+ return additionalKwargs?.lc_source === "summarization";
156
+ });
157
+ };
158
+ const SUMMARIZATION_MIDDLEWARE_NODE = "summarizationmiddleware.before_model";
159
+ const normalizeNodeMarker = (value)=>{
160
+ if ("string" != typeof value) return null;
161
+ const normalized = value.trim().toLowerCase();
162
+ return normalized.length > 0 ? normalized : null;
163
+ };
164
+ const extractSummarizationNodeCandidate = (value)=>{
165
+ if (!value || "object" != typeof value || Array.isArray(value)) return null;
166
+ const record = value;
167
+ const directCandidates = [
168
+ record.langgraph_node,
169
+ record.langgraphNode,
170
+ record.node,
171
+ record.node_id,
172
+ record.nodeId
173
+ ];
174
+ for (const candidate of directCandidates){
175
+ const normalized = normalizeNodeMarker(candidate);
176
+ if (normalized) return normalized;
177
+ }
178
+ const tagCandidates = [
179
+ record.tags,
180
+ record.ls_tags
181
+ ];
182
+ for (const tags of tagCandidates)if (Array.isArray(tags)) for (const tag of tags){
183
+ if ("string" != typeof tag) continue;
184
+ const normalizedTag = tag.trim().toLowerCase();
185
+ if (normalizedTag) {
186
+ if (normalizedTag === `langgraph_node:${SUMMARIZATION_MIDDLEWARE_NODE}`) return SUMMARIZATION_MIDDLEWARE_NODE;
187
+ if (normalizedTag === `langgraph_node=${SUMMARIZATION_MIDDLEWARE_NODE}`) return SUMMARIZATION_MIDDLEWARE_NODE;
188
+ }
189
+ }
190
+ return null;
191
+ };
192
+ const chunkBelongsToSummarizationMiddleware = (chunk)=>{
193
+ if (!chunk || "object" != typeof chunk || Array.isArray(chunk)) return false;
194
+ const eventChunk = chunk;
195
+ const nameNode = normalizeNodeMarker(eventChunk.name);
196
+ if (nameNode === SUMMARIZATION_MIDDLEWARE_NODE) return true;
197
+ const metadataCandidates = [
198
+ eventChunk.metadata,
199
+ eventChunk.data?.metadata,
200
+ eventChunk.data?.chunk,
201
+ eventChunk.data?.message
202
+ ];
203
+ for (const candidate of metadataCandidates){
204
+ const node = extractSummarizationNodeCandidate(candidate);
205
+ if (node === SUMMARIZATION_MIDDLEWARE_NODE) return true;
206
+ }
207
+ return false;
208
+ };
209
+ const SUMMARIZATION_ACTIVE_EVENTS = new Set([
210
+ "on_chat_model_start",
211
+ "on_chat_model_stream",
212
+ "on_chat_model_end",
213
+ "on_llm_start",
214
+ "on_llm_stream",
215
+ "on_llm_end"
216
+ ]);
217
+ const chunkSignalsActiveSummarization = (chunk)=>{
218
+ if (!chunkBelongsToSummarizationMiddleware(chunk)) return false;
219
+ if (!chunk || "object" != typeof chunk || Array.isArray(chunk)) return false;
220
+ const eventName = chunk.event;
221
+ return "string" == typeof eventName && SUMMARIZATION_ACTIVE_EVENTS.has(eventName);
222
+ };
137
223
  const chunkHasAssistantText = (chunk)=>{
138
224
  if (!chunk || "object" != typeof chunk || Array.isArray(chunk)) return false;
139
225
  const eventChunk = chunk;
@@ -195,6 +281,202 @@ const detectStreamErrorMessage = (chunk)=>{
195
281
  if (null != errorPayload) return String(errorPayload);
196
282
  return eventName;
197
283
  };
284
+ const getFiniteTokenNumber = (value)=>"number" == typeof value && Number.isFinite(value) ? value : 0;
285
+ const asRecord = (value)=>value && "object" == typeof value && !Array.isArray(value) ? value : null;
286
+ const collectTokenUsageSnapshot = (target, payload, visited, depth)=>{
287
+ if (depth > 8 || !payload || "object" != typeof payload) return;
288
+ if (visited.has(payload)) return;
289
+ visited.add(payload);
290
+ const record = payload;
291
+ const directInput = getFiniteTokenNumber(record.input_tokens) || getFiniteTokenNumber(record.inputTokens) || getFiniteTokenNumber(record.prompt_tokens) || getFiniteTokenNumber(record.promptTokens);
292
+ const directOutput = getFiniteTokenNumber(record.output_tokens) || getFiniteTokenNumber(record.outputTokens) || getFiniteTokenNumber(record.completion_tokens) || getFiniteTokenNumber(record.completionTokens);
293
+ const directTotal = getFiniteTokenNumber(record.total_tokens) || getFiniteTokenNumber(record.totalTokens);
294
+ if (directInput > 0) target.inputTokens = Math.max(target.inputTokens, directInput);
295
+ if (directOutput > 0) target.outputTokens = Math.max(target.outputTokens, directOutput);
296
+ if (directTotal > 0) target.totalTokens = Math.max(target.totalTokens, directTotal);
297
+ const nestedCandidates = [
298
+ record.usage,
299
+ record.usage_metadata,
300
+ record.usageMetadata,
301
+ record.tokenUsage,
302
+ record.response_metadata,
303
+ record.responseMetadata,
304
+ record.additional_kwargs,
305
+ record.additionalKwargs,
306
+ record.metadata,
307
+ record.data,
308
+ record.output,
309
+ record.message,
310
+ record.chunk
311
+ ];
312
+ for (const nested of nestedCandidates)collectTokenUsageSnapshot(target, nested, visited, depth + 1);
313
+ };
314
+ const extractTokenUsageSnapshot = (payload)=>{
315
+ const snapshot = {
316
+ inputTokens: 0,
317
+ outputTokens: 0,
318
+ totalTokens: 0
319
+ };
320
+ const visited = new WeakSet();
321
+ collectTokenUsageSnapshot(snapshot, payload, visited, 0);
322
+ if (0 === snapshot.totalTokens) snapshot.totalTokens = snapshot.inputTokens + snapshot.outputTokens;
323
+ if (snapshot.inputTokens <= 0 && snapshot.outputTokens <= 0 && snapshot.totalTokens <= 0) return null;
324
+ return snapshot;
325
+ };
326
+ const getMessageClassName = (message)=>{
327
+ const id = message.id;
328
+ if (Array.isArray(id) && id.length > 0) {
329
+ const tail = id[id.length - 1];
330
+ if ("string" == typeof tail) return tail.trim().toLowerCase();
331
+ }
332
+ const type = "string" == typeof message.type ? message.type : "";
333
+ return type.trim().toLowerCase();
334
+ };
335
+ const getMessageRole = (message)=>{
336
+ const kwargs = asRecord(message.kwargs);
337
+ const additionalKwargs = asRecord(message.additional_kwargs);
338
+ const additionalKwargsCamel = asRecord(message.additionalKwargs);
339
+ const candidates = [
340
+ message.role,
341
+ kwargs?.role,
342
+ additionalKwargs?.role,
343
+ additionalKwargsCamel?.role
344
+ ];
345
+ for (const candidate of candidates)if ("string" == typeof candidate && candidate.trim()) return candidate.trim().toLowerCase();
346
+ return "";
347
+ };
348
+ const isMessageLikeRecord = (value)=>{
349
+ const record = asRecord(value);
350
+ if (!record) return false;
351
+ const role = getMessageRole(record);
352
+ if ("user" === role || "human" === role || "assistant" === role || "ai" === role || "system" === role || "tool" === role) return true;
353
+ const className = getMessageClassName(record);
354
+ if (className.includes("humanmessage") || className.includes("aimessage") || className.includes("toolmessage") || className.includes("systemmessage")) return true;
355
+ return "human" === className || "user" === className || "assistant" === className || "ai" === className || "system" === className || "tool" === className;
356
+ };
357
+ const extractTextFromContent = (content)=>{
358
+ if ("string" == typeof content) return content;
359
+ if (!Array.isArray(content)) return "";
360
+ return content.map((item)=>{
361
+ if ("string" == typeof item) return item;
362
+ const record = asRecord(item);
363
+ if (!record) return "";
364
+ if ("text" === record.type && "string" == typeof record.text) return record.text;
365
+ return "string" == typeof record.text ? record.text : "";
366
+ }).join("");
367
+ };
368
+ const extractMessageContent = (message)=>{
369
+ const kwargs = asRecord(message.kwargs);
370
+ const additionalKwargs = asRecord(message.additional_kwargs);
371
+ const additionalKwargsCamel = asRecord(message.additionalKwargs);
372
+ const candidates = [
373
+ message.content,
374
+ kwargs?.content,
375
+ additionalKwargs?.content,
376
+ additionalKwargsCamel?.content
377
+ ];
378
+ for (const candidate of candidates){
379
+ const extracted = extractTextFromContent(candidate);
380
+ if (extracted.length > 0) return extracted;
381
+ }
382
+ return "";
383
+ };
384
+ const extractToolCalls = (message)=>{
385
+ const kwargs = asRecord(message.kwargs);
386
+ const candidates = [
387
+ message.tool_calls,
388
+ message.toolCalls,
389
+ kwargs?.tool_calls,
390
+ kwargs?.toolCalls
391
+ ];
392
+ for (const candidate of candidates)if (Array.isArray(candidate) && candidate.length > 0) return candidate;
393
+ return [];
394
+ };
395
+ const extractToolCallId = (message)=>{
396
+ const kwargs = asRecord(message.kwargs);
397
+ const candidates = [
398
+ message.tool_call_id,
399
+ message.toolCallId,
400
+ kwargs?.tool_call_id,
401
+ kwargs?.toolCallId
402
+ ];
403
+ for (const candidate of candidates)if ("string" == typeof candidate && candidate.trim()) return candidate.trim();
404
+ return "";
405
+ };
406
+ const isAiMessageRecord = (message)=>{
407
+ const role = getMessageRole(message);
408
+ if ("assistant" === role || "ai" === role) return true;
409
+ const className = getMessageClassName(message);
410
+ return "ai" === className || "assistant" === className || className.includes("aimessage");
411
+ };
412
+ const isToolMessageRecord = (message)=>{
413
+ const role = getMessageRole(message);
414
+ if ("tool" === role) return true;
415
+ const className = getMessageClassName(message);
416
+ return "tool" === className || className.includes("toolmessage");
417
+ };
418
+ const estimateTokensForMessageArray = (messages)=>{
419
+ if (0 === messages.length) return 0;
420
+ let totalChars = 0;
421
+ for (const message of messages){
422
+ let textContent = extractMessageContent(message);
423
+ if (isAiMessageRecord(message)) {
424
+ const toolCalls = extractToolCalls(message);
425
+ if (toolCalls.length > 0) textContent += JSON.stringify(toolCalls);
426
+ }
427
+ if (isToolMessageRecord(message)) textContent += extractToolCallId(message);
428
+ totalChars += textContent.length;
429
+ }
430
+ return Math.ceil(totalChars / 4);
431
+ };
432
+ const collectMessageArraysFromPayload = (target, payload, visited, depth)=>{
433
+ if (depth > 7 || !payload || "object" != typeof payload) return;
434
+ if (visited.has(payload)) return;
435
+ visited.add(payload);
436
+ if (Array.isArray(payload)) {
437
+ const messageRecords = payload.filter(isMessageLikeRecord);
438
+ if (messageRecords.length > 0) target.push(messageRecords);
439
+ for (const item of payload)collectMessageArraysFromPayload(target, item, visited, depth + 1);
440
+ return;
441
+ }
442
+ const record = payload;
443
+ const directMessages = record.messages;
444
+ if (Array.isArray(directMessages)) {
445
+ const messageRecords = directMessages.filter(isMessageLikeRecord);
446
+ if (messageRecords.length > 0) target.push(messageRecords);
447
+ }
448
+ for (const nested of Object.values(record))if (nested && "object" == typeof nested) collectMessageArraysFromPayload(target, nested, visited, depth + 1);
449
+ };
450
+ const estimateContextTokensFromChunk = (chunk)=>{
451
+ const candidates = [];
452
+ collectMessageArraysFromPayload(candidates, chunk, new WeakSet(), 0);
453
+ if (0 === candidates.length) return null;
454
+ let estimate = 0;
455
+ for (const candidate of candidates){
456
+ const tokens = estimateTokensForMessageArray(candidate);
457
+ if (tokens > estimate) estimate = tokens;
458
+ }
459
+ return estimate > 0 ? estimate : null;
460
+ };
461
+ const detectContextSummarizationTransition = ({ thresholdTokens, peakInputTokens, currentInputTokens })=>{
462
+ if (!Number.isFinite(thresholdTokens) || !Number.isFinite(peakInputTokens) || !Number.isFinite(currentInputTokens)) return false;
463
+ if (thresholdTokens <= 0 || peakInputTokens <= 0 || currentInputTokens <= 0) return false;
464
+ if (peakInputTokens < 0.9 * thresholdTokens) return false;
465
+ if (currentInputTokens > 0.65 * thresholdTokens) return false;
466
+ if (currentInputTokens > 0.75 * peakInputTokens) return false;
467
+ return true;
468
+ };
469
+ const mergeTokenUsageSnapshots = (current, next)=>{
470
+ if (!next) return current;
471
+ if (!current) return next;
472
+ const merged = {
473
+ inputTokens: Math.max(current.inputTokens, next.inputTokens),
474
+ outputTokens: Math.max(current.outputTokens, next.outputTokens),
475
+ totalTokens: Math.max(current.totalTokens, next.totalTokens)
476
+ };
477
+ if (0 === merged.totalTokens) merged.totalTokens = merged.inputTokens + merged.outputTokens;
478
+ return merged;
479
+ };
198
480
  const extractStreamEventRecord = (chunk)=>{
199
481
  if (!chunk || "object" != typeof chunk || Array.isArray(chunk)) return null;
200
482
  const record = chunk;
@@ -434,7 +716,7 @@ class AgentInvoker {
434
716
  rootDir: outputMount.absolutePath,
435
717
  virtualMode: true
436
718
  });
437
- const standaloneAgent = createDeepAgent({
719
+ let standaloneAgent = createDeepAgent({
438
720
  systemPrompt: targetAgent.systemPrompt,
439
721
  tools: targetAgent.tools,
440
722
  model: targetAgent.model,
@@ -450,10 +732,15 @@ class AgentInvoker {
450
732
  checkpointer: checkpointer
451
733
  });
452
734
  configureDeepAgentSummarizationMiddleware(standaloneAgent, summarizationSettings, targetAgent.model);
735
+ standaloneAgent = recompileDeepAgentWithMiddlewareOverrides(standaloneAgent);
453
736
  this.logger.debug("Agent created, sending message");
454
737
  const userContent = buildUserContent(prompt, attachments, targetAgent.model);
455
738
  if (this.sessionManager && sessionId) {
456
739
  this.logger.debug(`Using streaming with session: ${sessionId}`);
740
+ let streamTokenUsage = null;
741
+ let streamEstimatedContextTokens = 0;
742
+ let contextSummarizationStarted = false;
743
+ let contextSummarizationEmitted = false;
457
744
  const stream = await standaloneAgent.streamEvents({
458
745
  messages: [
459
746
  {
@@ -486,7 +773,32 @@ class AgentInvoker {
486
773
  cancelled: true
487
774
  };
488
775
  }
489
- this.outputManager.emitAgentStream(chunk);
776
+ const chunkTokenUsage = extractTokenUsageSnapshot(chunk);
777
+ streamTokenUsage = mergeTokenUsageSnapshots(streamTokenUsage, chunkTokenUsage);
778
+ const isSummarizationChunk = chunkBelongsToSummarizationMiddleware(chunk);
779
+ const isActiveSummarizationChunk = chunkSignalsActiveSummarization(chunk);
780
+ if (isActiveSummarizationChunk && !contextSummarizationStarted) {
781
+ contextSummarizationStarted = true;
782
+ this.outputManager.emitContextSummarizing();
783
+ }
784
+ if (!isSummarizationChunk) {
785
+ const chunkEstimatedContextTokens = estimateContextTokensFromChunk(chunk);
786
+ if ("number" == typeof chunkEstimatedContextTokens && Number.isFinite(chunkEstimatedContextTokens) && chunkEstimatedContextTokens > streamEstimatedContextTokens) streamEstimatedContextTokens = chunkEstimatedContextTokens;
787
+ this.outputManager.emitAgentStream(chunk, chunkTokenUsage || void 0, streamEstimatedContextTokens > 0 ? streamEstimatedContextTokens : void 0);
788
+ }
789
+ if (!contextSummarizationEmitted && summarizationSettings && chunkHasBuiltInSummarizationSignal(chunk)) {
790
+ if (!contextSummarizationStarted) {
791
+ contextSummarizationStarted = true;
792
+ this.outputManager.emitContextSummarizing();
793
+ }
794
+ contextSummarizationEmitted = true;
795
+ const observedInputTokens = chunkTokenUsage?.inputTokens || 0;
796
+ this.outputManager.emitContextSummarized({
797
+ inputTokens: observedInputTokens,
798
+ peakInputTokens: observedInputTokens,
799
+ thresholdTokens: summarizationSettings.maxTokensBeforeSummary
800
+ });
801
+ }
490
802
  if (isRootLangGraphTerminalEvent(chunk, rootLangGraphRunId)) {
491
803
  this.logger.debug("Detected root LangGraph on_chain_end event; finalizing stream without waiting for iterator shutdown");
492
804
  break;
@@ -501,7 +813,10 @@ class AgentInvoker {
501
813
  };
502
814
  }
503
815
  this.logger.info("Agent streaming completed successfully");
504
- const completionPayload = {
816
+ const completionPayload = streamTokenUsage ? {
817
+ streaming: true,
818
+ tokenUsage: streamTokenUsage
819
+ } : {
505
820
  streaming: true
506
821
  };
507
822
  emitCompletionAndContinuePostProcessing({
@@ -815,4 +1130,4 @@ function buildAttachmentPreview(attachments) {
815
1130
  if (hasImage) return "[image]";
816
1131
  return "";
817
1132
  }
818
- export { AGENTS_MEMORY_VIRTUAL_PATHS, AgentInvoker, OUTPUT_VIRTUAL_PATH, WORKDIR_VIRTUAL_PATH, buildUserContent, chunkHasAssistantText, configureDeepAgentSummarizationMiddleware, detectStreamErrorMessage, detectToolEventContext, emitCompletionAndContinuePostProcessing, isRootLangGraphTerminalEvent, resolveAgentExecutionWorkspace, resolveAgentMemorySources, resolveExecutionWorkspace, resolveExternalOutputMount, resolveHumanInTheLoopSettings, resolveModelRetryMiddlewareSettings, resolveSummarizationMiddlewareSettings, resolveToolRetryMiddlewareSettings, selectStreamingFallbackText, toWorkspaceAliasVirtualPath, trackRootLangGraphRunId };
1133
+ export { AGENTS_MEMORY_VIRTUAL_PATHS, AgentInvoker, OUTPUT_VIRTUAL_PATH, WORKDIR_VIRTUAL_PATH, buildUserContent, chunkBelongsToSummarizationMiddleware, chunkHasAssistantText, chunkHasBuiltInSummarizationSignal, chunkSignalsActiveSummarization, configureDeepAgentSummarizationMiddleware, detectContextSummarizationTransition, detectStreamErrorMessage, detectToolEventContext, emitCompletionAndContinuePostProcessing, estimateContextTokensFromChunk, extractTokenUsageSnapshot, isRootLangGraphTerminalEvent, mergeTokenUsageSnapshots, recompileDeepAgentWithMiddlewareOverrides, resolveAgentExecutionWorkspace, resolveAgentMemorySources, resolveExecutionWorkspace, resolveExternalOutputMount, resolveHumanInTheLoopSettings, resolveModelRetryMiddlewareSettings, resolveSummarizationMiddlewareSettings, resolveToolRetryMiddlewareSettings, selectStreamingFallbackText, toWorkspaceAliasVirtualPath, trackRootLangGraphRunId };
@@ -66,10 +66,31 @@ class OutputManager extends external_node_events_namespaceObject.EventEmitter {
66
66
  timestamp: new Date().toISOString()
67
67
  });
68
68
  }
69
- emitAgentStream(chunk) {
69
+ emitAgentStream(chunk, tokenUsage, estimatedContextTokens) {
70
70
  this.emitEvent({
71
71
  type: "agent-stream",
72
72
  chunk,
73
+ ...tokenUsage ? {
74
+ tokenUsage
75
+ } : {},
76
+ ..."number" == typeof estimatedContextTokens && Number.isFinite(estimatedContextTokens) && estimatedContextTokens > 0 ? {
77
+ estimatedContextTokens: Math.round(estimatedContextTokens)
78
+ } : {},
79
+ timestamp: new Date().toISOString()
80
+ });
81
+ }
82
+ emitContextSummarizing() {
83
+ this.emitEvent({
84
+ type: "context-summarizing",
85
+ timestamp: new Date().toISOString()
86
+ });
87
+ }
88
+ emitContextSummarized(payload) {
89
+ this.emitEvent({
90
+ type: "context-summarized",
91
+ inputTokens: payload.inputTokens,
92
+ peakInputTokens: payload.peakInputTokens,
93
+ thresholdTokens: payload.thresholdTokens,
73
94
  timestamp: new Date().toISOString()
74
95
  });
75
96
  }
@@ -31,7 +31,23 @@ export declare class OutputManager extends EventEmitter {
31
31
  * Emit agent stream chunk
32
32
  * Forwards raw chunks from deepagents/LangGraph for client-side interpretation
33
33
  */
34
- emitAgentStream(chunk: any): void;
34
+ emitAgentStream(chunk: any, tokenUsage?: {
35
+ inputTokens: number;
36
+ outputTokens: number;
37
+ totalTokens: number;
38
+ }, estimatedContextTokens?: number): void;
39
+ /**
40
+ * Emit explicit context summarization start signal
41
+ */
42
+ emitContextSummarizing(): void;
43
+ /**
44
+ * Emit explicit context summarization signal
45
+ */
46
+ emitContextSummarized(payload: {
47
+ inputTokens: number;
48
+ peakInputTokens: number;
49
+ thresholdTokens: number;
50
+ }): void;
35
51
  /**
36
52
  * Emit agent completion
37
53
  */
@@ -38,10 +38,31 @@ class OutputManager extends EventEmitter {
38
38
  timestamp: new Date().toISOString()
39
39
  });
40
40
  }
41
- emitAgentStream(chunk) {
41
+ emitAgentStream(chunk, tokenUsage, estimatedContextTokens) {
42
42
  this.emitEvent({
43
43
  type: "agent-stream",
44
44
  chunk,
45
+ ...tokenUsage ? {
46
+ tokenUsage
47
+ } : {},
48
+ ..."number" == typeof estimatedContextTokens && Number.isFinite(estimatedContextTokens) && estimatedContextTokens > 0 ? {
49
+ estimatedContextTokens: Math.round(estimatedContextTokens)
50
+ } : {},
51
+ timestamp: new Date().toISOString()
52
+ });
53
+ }
54
+ emitContextSummarizing() {
55
+ this.emitEvent({
56
+ type: "context-summarizing",
57
+ timestamp: new Date().toISOString()
58
+ });
59
+ }
60
+ emitContextSummarized(payload) {
61
+ this.emitEvent({
62
+ type: "context-summarized",
63
+ inputTokens: payload.inputTokens,
64
+ peakInputTokens: payload.peakInputTokens,
65
+ thresholdTokens: payload.thresholdTokens,
45
66
  timestamp: new Date().toISOString()
46
67
  });
47
68
  }
@@ -130,6 +130,23 @@ export interface AgentStartEvent {
130
130
  export interface AgentStreamEvent {
131
131
  type: "agent-stream";
132
132
  chunk: any;
133
+ tokenUsage?: {
134
+ inputTokens: number;
135
+ outputTokens: number;
136
+ totalTokens: number;
137
+ };
138
+ estimatedContextTokens?: number;
139
+ timestamp: string;
140
+ }
141
+ export interface AgentContextSummarizingEvent {
142
+ type: "context-summarizing";
143
+ timestamp: string;
144
+ }
145
+ export interface AgentContextSummarizedEvent {
146
+ type: "context-summarized";
147
+ inputTokens: number;
148
+ peakInputTokens: number;
149
+ thresholdTokens: number;
133
150
  timestamp: string;
134
151
  }
135
152
  export interface AgentCompleteEvent {
@@ -178,7 +195,7 @@ export interface SkillRemoveEvent {
178
195
  skill: string;
179
196
  timestamp: string;
180
197
  }
181
- export type OutputEvent = LogEvent | AgentStartEvent | AgentStreamEvent | AgentCompleteEvent | AgentErrorEvent | SkillBrowseEvent | SkillInstallProgressEvent | SkillInstallCompleteEvent | SkillListEvent | SkillRemoveEvent;
198
+ export type OutputEvent = LogEvent | AgentStartEvent | AgentStreamEvent | AgentContextSummarizingEvent | AgentContextSummarizedEvent | AgentCompleteEvent | AgentErrorEvent | SkillBrowseEvent | SkillInstallProgressEvent | SkillInstallCompleteEvent | SkillListEvent | SkillRemoveEvent;
182
199
  export interface TextBlock {
183
200
  content: string;
184
201
  isStreaming: boolean;
@@ -203,6 +203,8 @@ const App = ({ outputManager })=>{
203
203
  break;
204
204
  }
205
205
  break;
206
+ case "context-summarized":
207
+ break;
206
208
  case "agent-complete":
207
209
  setIsStreaming(false);
208
210
  setIsComplete(true);
@@ -165,6 +165,8 @@ const App = ({ outputManager })=>{
165
165
  break;
166
166
  }
167
167
  break;
168
+ case "context-summarized":
169
+ break;
168
170
  case "agent-complete":
169
171
  setIsStreaming(false);
170
172
  setIsComplete(true);
@@ -1652,6 +1652,7 @@ class GatewayServer {
1652
1652
  defaultAgentId,
1653
1653
  outputRoot: this.resolveOutputRoot(),
1654
1654
  dynamicUiEnabled: this.wingmanConfig.gateway?.dynamicUiEnabled !== false,
1655
+ summarization: this.wingmanConfig.summarization,
1655
1656
  voice: this.wingmanConfig.voice,
1656
1657
  agents
1657
1658
  }, null, 2), {
@@ -1617,6 +1617,7 @@ class GatewayServer {
1617
1617
  defaultAgentId,
1618
1618
  outputRoot: this.resolveOutputRoot(),
1619
1619
  dynamicUiEnabled: this.wingmanConfig.gateway?.dynamicUiEnabled !== false,
1620
+ summarization: this.wingmanConfig.summarization,
1620
1621
  voice: this.wingmanConfig.voice,
1621
1622
  agents
1622
1623
  }, null, 2), {