From 5cc8f040f46766360348d044fbfe009ac3853506 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 13 Jul 2026 11:40:14 -0700 Subject: [PATCH 1/3] Centralize run lifecycle and enable strict templates --- cockpit/ag-ui/a2ui/angular/tsconfig.json | 3 +- .../ag-ui/client-tools/angular/tsconfig.json | 3 +- .../ag-ui/interrupts/angular/tsconfig.json | 3 +- .../ag-ui/json-render/angular/tsconfig.json | 3 +- cockpit/ag-ui/streaming/angular/tsconfig.json | 3 +- cockpit/ag-ui/subagents/angular/tsconfig.json | 3 +- .../ag-ui/tool-views/angular/tsconfig.json | 3 +- cockpit/chat/a2ui/angular/tsconfig.json | 3 +- cockpit/chat/debug/angular/tsconfig.json | 3 +- .../chat/generative-ui/angular/tsconfig.json | 3 +- .../input/angular/src/app/input.component.ts | 7 ++- cockpit/chat/input/angular/tsconfig.json | 3 +- cockpit/chat/interrupts/angular/tsconfig.json | 3 +- .../angular/src/app/messages.component.ts | 7 ++- cockpit/chat/messages/angular/tsconfig.json | 3 +- cockpit/chat/subagents/angular/tsconfig.json | 3 +- cockpit/chat/theming/angular/tsconfig.json | 3 +- cockpit/chat/threads/angular/tsconfig.json | 3 +- cockpit/chat/timeline/angular/tsconfig.json | 3 +- cockpit/chat/tool-calls/angular/tsconfig.json | 3 +- .../filesystem/angular/tsconfig.json | 3 +- .../deep-agents/memory/angular/tsconfig.json | 3 +- .../planning/angular/tsconfig.json | 3 +- .../sandboxes/angular/tsconfig.json | 3 +- .../deep-agents/skills/angular/tsconfig.json | 3 +- .../subagents/angular/tsconfig.json | 3 +- .../client-tools/angular/tsconfig.json | 3 +- .../deployment-runtime/angular/tsconfig.json | 3 +- .../durable-execution/angular/tsconfig.json | 3 +- .../interrupts/angular/tsconfig.json | 3 +- .../langgraph/memory/angular/tsconfig.json | 3 +- .../persistence/angular/tsconfig.json | 3 +- .../langgraph/streaming/angular/tsconfig.json | 3 +- .../langgraph/subgraphs/angular/tsconfig.json | 3 +- .../time-travel/angular/tsconfig.json | 3 +- .../computed-functions/angular/tsconfig.json | 3 +- .../element-rendering/angular/tsconfig.json | 3 +- cockpit/render/registry/angular/tsconfig.json | 3 +- .../render/repeat-loops/angular/tsconfig.json | 3 +- .../spec-rendering/angular/tsconfig.json | 3 +- .../state-management/angular/tsconfig.json | 3 +- examples/ag-ui/angular/e2e/test-helpers.ts | 2 +- examples/ag-ui/angular/tsconfig.json | 3 +- examples/chat/angular/e2e/test-helpers.ts | 2 +- examples/chat/angular/tsconfig.json | 3 +- examples/chat/smoke/template/tsconfig.json | 2 +- libs/ag-ui/src/lib/client-tools.spec.ts | 26 ++++---- libs/ag-ui/src/lib/client-tools.ts | 14 +++-- libs/ag-ui/src/lib/to-agent.spec.ts | 56 ++++++++++++++++++ libs/ag-ui/src/lib/to-agent.ts | 59 ++++++++----------- .../chat/src/lib/agent/agent-ref.type-spec.ts | 2 +- libs/chat/src/lib/agent/agent-with-history.ts | 2 +- libs/chat/src/lib/agent/agent.ts | 2 +- .../compositions/chat/chat.component.spec.ts | 27 +++++++++ .../lib/compositions/chat/chat.component.ts | 14 ++--- tsconfig.base.json | 3 + 56 files changed, 194 insertions(+), 154 deletions(-) diff --git a/cockpit/ag-ui/a2ui/angular/tsconfig.json b/cockpit/ag-ui/a2ui/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/ag-ui/a2ui/angular/tsconfig.json +++ b/cockpit/ag-ui/a2ui/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/ag-ui/client-tools/angular/tsconfig.json b/cockpit/ag-ui/client-tools/angular/tsconfig.json index 00d3e70aa..a5e3000ac 100644 --- a/cockpit/ag-ui/client-tools/angular/tsconfig.json +++ b/cockpit/ag-ui/client-tools/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/ag-ui/interrupts/angular/tsconfig.json b/cockpit/ag-ui/interrupts/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/ag-ui/interrupts/angular/tsconfig.json +++ b/cockpit/ag-ui/interrupts/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/ag-ui/json-render/angular/tsconfig.json b/cockpit/ag-ui/json-render/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/ag-ui/json-render/angular/tsconfig.json +++ b/cockpit/ag-ui/json-render/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/ag-ui/streaming/angular/tsconfig.json b/cockpit/ag-ui/streaming/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/ag-ui/streaming/angular/tsconfig.json +++ b/cockpit/ag-ui/streaming/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/ag-ui/subagents/angular/tsconfig.json b/cockpit/ag-ui/subagents/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/ag-ui/subagents/angular/tsconfig.json +++ b/cockpit/ag-ui/subagents/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/ag-ui/tool-views/angular/tsconfig.json b/cockpit/ag-ui/tool-views/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/ag-ui/tool-views/angular/tsconfig.json +++ b/cockpit/ag-ui/tool-views/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/a2ui/angular/tsconfig.json b/cockpit/chat/a2ui/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/chat/a2ui/angular/tsconfig.json +++ b/cockpit/chat/a2ui/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/debug/angular/tsconfig.json b/cockpit/chat/debug/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/chat/debug/angular/tsconfig.json +++ b/cockpit/chat/debug/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/generative-ui/angular/tsconfig.json b/cockpit/chat/generative-ui/angular/tsconfig.json index af65cf0a6..d024e7184 100644 --- a/cockpit/chat/generative-ui/angular/tsconfig.json +++ b/cockpit/chat/generative-ui/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/input/angular/src/app/input.component.ts b/cockpit/chat/input/angular/src/app/input.component.ts index e69ee8f5f..65a22ae26 100644 --- a/cockpit/chat/input/angular/src/app/input.component.ts +++ b/cockpit/chat/input/angular/src/app/input.component.ts @@ -6,6 +6,7 @@ import { ChatMessageComponent, ChatStreamingMdComponent, MessageTemplateDirective, + markdownDocument, messageContent, } from '@threadplane/chat'; import { ExampleChatLayoutComponent } from '@threadplane/example-layouts'; @@ -46,12 +47,11 @@ import { injectAgent } from '@threadplane/langgraph'; @@ -184,4 +184,5 @@ export class InputComponent { protected readonly streamStatus = computed(() => this.agent.status()); protected readonly isLoading = computed(() => this.agent.isLoading()); protected readonly messageContent = messageContent; + protected readonly markdownDocument = markdownDocument; } diff --git a/cockpit/chat/input/angular/tsconfig.json b/cockpit/chat/input/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/chat/input/angular/tsconfig.json +++ b/cockpit/chat/input/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/interrupts/angular/tsconfig.json b/cockpit/chat/interrupts/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/chat/interrupts/angular/tsconfig.json +++ b/cockpit/chat/interrupts/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/messages/angular/src/app/messages.component.ts b/cockpit/chat/messages/angular/src/app/messages.component.ts index 779c0b6f4..69dbb7d07 100644 --- a/cockpit/chat/messages/angular/src/app/messages.component.ts +++ b/cockpit/chat/messages/angular/src/app/messages.component.ts @@ -7,6 +7,7 @@ import { ChatMessageComponent, ChatStreamingMdComponent, MessageTemplateDirective, + markdownDocument, messageContent, } from '@threadplane/chat'; import { ExampleChatLayoutComponent } from '@threadplane/example-layouts'; @@ -52,12 +53,11 @@ import { MESSAGES_AGENT, type MessagesState } from './agent-ref'; @@ -157,6 +157,7 @@ export class MessagesComponent { protected readonly _typedState: MessagesState = this.agent.value(); protected readonly messageContent = messageContent; + protected readonly markdownDocument = markdownDocument; submitMessage(content: string) { this.agent.submit({ message: content }); diff --git a/cockpit/chat/messages/angular/tsconfig.json b/cockpit/chat/messages/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/chat/messages/angular/tsconfig.json +++ b/cockpit/chat/messages/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/subagents/angular/tsconfig.json b/cockpit/chat/subagents/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/chat/subagents/angular/tsconfig.json +++ b/cockpit/chat/subagents/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/theming/angular/tsconfig.json b/cockpit/chat/theming/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/chat/theming/angular/tsconfig.json +++ b/cockpit/chat/theming/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/threads/angular/tsconfig.json b/cockpit/chat/threads/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/chat/threads/angular/tsconfig.json +++ b/cockpit/chat/threads/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/timeline/angular/tsconfig.json b/cockpit/chat/timeline/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/chat/timeline/angular/tsconfig.json +++ b/cockpit/chat/timeline/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/chat/tool-calls/angular/tsconfig.json b/cockpit/chat/tool-calls/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/chat/tool-calls/angular/tsconfig.json +++ b/cockpit/chat/tool-calls/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/deep-agents/filesystem/angular/tsconfig.json b/cockpit/deep-agents/filesystem/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/deep-agents/filesystem/angular/tsconfig.json +++ b/cockpit/deep-agents/filesystem/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/deep-agents/memory/angular/tsconfig.json b/cockpit/deep-agents/memory/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/deep-agents/memory/angular/tsconfig.json +++ b/cockpit/deep-agents/memory/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/deep-agents/planning/angular/tsconfig.json b/cockpit/deep-agents/planning/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/deep-agents/planning/angular/tsconfig.json +++ b/cockpit/deep-agents/planning/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/deep-agents/sandboxes/angular/tsconfig.json b/cockpit/deep-agents/sandboxes/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/deep-agents/sandboxes/angular/tsconfig.json +++ b/cockpit/deep-agents/sandboxes/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/deep-agents/skills/angular/tsconfig.json b/cockpit/deep-agents/skills/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/deep-agents/skills/angular/tsconfig.json +++ b/cockpit/deep-agents/skills/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/deep-agents/subagents/angular/tsconfig.json b/cockpit/deep-agents/subagents/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/deep-agents/subagents/angular/tsconfig.json +++ b/cockpit/deep-agents/subagents/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/langgraph/client-tools/angular/tsconfig.json b/cockpit/langgraph/client-tools/angular/tsconfig.json index 00d3e70aa..a5e3000ac 100644 --- a/cockpit/langgraph/client-tools/angular/tsconfig.json +++ b/cockpit/langgraph/client-tools/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/langgraph/deployment-runtime/angular/tsconfig.json b/cockpit/langgraph/deployment-runtime/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/langgraph/deployment-runtime/angular/tsconfig.json +++ b/cockpit/langgraph/deployment-runtime/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/langgraph/durable-execution/angular/tsconfig.json b/cockpit/langgraph/durable-execution/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/langgraph/durable-execution/angular/tsconfig.json +++ b/cockpit/langgraph/durable-execution/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/langgraph/interrupts/angular/tsconfig.json b/cockpit/langgraph/interrupts/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/langgraph/interrupts/angular/tsconfig.json +++ b/cockpit/langgraph/interrupts/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/langgraph/memory/angular/tsconfig.json b/cockpit/langgraph/memory/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/langgraph/memory/angular/tsconfig.json +++ b/cockpit/langgraph/memory/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/langgraph/persistence/angular/tsconfig.json b/cockpit/langgraph/persistence/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/langgraph/persistence/angular/tsconfig.json +++ b/cockpit/langgraph/persistence/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/langgraph/streaming/angular/tsconfig.json b/cockpit/langgraph/streaming/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/langgraph/streaming/angular/tsconfig.json +++ b/cockpit/langgraph/streaming/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/langgraph/subgraphs/angular/tsconfig.json b/cockpit/langgraph/subgraphs/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/langgraph/subgraphs/angular/tsconfig.json +++ b/cockpit/langgraph/subgraphs/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/langgraph/time-travel/angular/tsconfig.json b/cockpit/langgraph/time-travel/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/langgraph/time-travel/angular/tsconfig.json +++ b/cockpit/langgraph/time-travel/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/render/computed-functions/angular/tsconfig.json b/cockpit/render/computed-functions/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/render/computed-functions/angular/tsconfig.json +++ b/cockpit/render/computed-functions/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/render/element-rendering/angular/tsconfig.json b/cockpit/render/element-rendering/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/render/element-rendering/angular/tsconfig.json +++ b/cockpit/render/element-rendering/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/render/registry/angular/tsconfig.json b/cockpit/render/registry/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/render/registry/angular/tsconfig.json +++ b/cockpit/render/registry/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/render/repeat-loops/angular/tsconfig.json b/cockpit/render/repeat-loops/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/render/repeat-loops/angular/tsconfig.json +++ b/cockpit/render/repeat-loops/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/render/spec-rendering/angular/tsconfig.json b/cockpit/render/spec-rendering/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/render/spec-rendering/angular/tsconfig.json +++ b/cockpit/render/spec-rendering/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/cockpit/render/state-management/angular/tsconfig.json b/cockpit/render/state-management/angular/tsconfig.json index 3fd970371..dedbb5609 100644 --- a/cockpit/render/state-management/angular/tsconfig.json +++ b/cockpit/render/state-management/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/examples/ag-ui/angular/e2e/test-helpers.ts b/examples/ag-ui/angular/e2e/test-helpers.ts index 17b13e2da..72e5ce24c 100644 --- a/examples/ag-ui/angular/e2e/test-helpers.ts +++ b/examples/ag-ui/angular/e2e/test-helpers.ts @@ -190,7 +190,7 @@ export async function waitForFinalAssistant(page: Page): Promise { * Send a user prompt and wait for the assistant bubble to finalize. * * "Finalized" means `chat-message[data-role="assistant"][data-streaming="false"]`: - * the chat composition wires `[streaming]` to `agent.isLoading() && i === lastIndex` + * the chat composition wires `[streaming]` to the message delivery phase * on the latest assistant ``, so the attribute flips to `"false"` * once the agent stops loading and the markdown render has settled. * diff --git a/examples/ag-ui/angular/tsconfig.json b/examples/ag-ui/angular/tsconfig.json index 185f669ec..45f89b6b4 100644 --- a/examples/ag-ui/angular/tsconfig.json +++ b/examples/ag-ui/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/examples/chat/angular/e2e/test-helpers.ts b/examples/chat/angular/e2e/test-helpers.ts index 7535bc351..6b93b853f 100644 --- a/examples/chat/angular/e2e/test-helpers.ts +++ b/examples/chat/angular/e2e/test-helpers.ts @@ -197,7 +197,7 @@ export async function waitForFinalAssistant(page: Page): Promise { * Send a user prompt and wait for the assistant bubble to finalize. * * "Finalized" means `chat-message[data-role="assistant"][data-streaming="false"]`: - * the chat composition wires `[streaming]` to `agent.isLoading() && i === lastIndex` + * the chat composition wires `[streaming]` to the message delivery phase * on the latest assistant ``, so the attribute flips to `"false"` * once the agent stops loading and the markdown render has settled. * diff --git a/examples/chat/angular/tsconfig.json b/examples/chat/angular/tsconfig.json index 1139eb967..cc4b1f701 100644 --- a/examples/chat/angular/tsconfig.json +++ b/examples/chat/angular/tsconfig.json @@ -13,8 +13,7 @@ "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": false, - "strictInputAccessModifiers": false, - "strictTemplates": false + "strictInputAccessModifiers": false }, "files": [], "include": [], diff --git a/examples/chat/smoke/template/tsconfig.json b/examples/chat/smoke/template/tsconfig.json index 8f63b979f..70300fb2d 100644 --- a/examples/chat/smoke/template/tsconfig.json +++ b/examples/chat/smoke/template/tsconfig.json @@ -17,7 +17,7 @@ "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, - "strictTemplates": false + "strictTemplates": true }, "files": [], "references": [{ "path": "./tsconfig.app.json" }] diff --git a/libs/ag-ui/src/lib/client-tools.spec.ts b/libs/ag-ui/src/lib/client-tools.spec.ts index fb626294e..500901dd3 100644 --- a/libs/ag-ui/src/lib/client-tools.spec.ts +++ b/libs/ag-ui/src/lib/client-tools.spec.ts @@ -5,7 +5,7 @@ import { Subject } from 'rxjs'; import { type AgentError } from '@threadplane/chat'; import type { AgentEvent, AgentStatus, Message, ToolCall } from '@threadplane/chat'; import type { ReducerStore, CustomStreamEvent } from './reducer'; -import { createClientToolsCapability } from './client-tools'; +import { createClientToolsCapability as createCapability } from './client-tools'; /** Build a minimal ReducerStore backed by writable signals — mirrors reducer.spec.ts. */ function makeStore(): ReducerStore { @@ -26,10 +26,17 @@ function makeStore(): ReducerStore { function makeSource() { return { addMessage: vi.fn(), - runAgent: vi.fn(async () => ({ result: undefined, newMessages: [] })), + continueRun: vi.fn(async () => undefined), }; } +function createClientToolsCapability( + source: ReturnType, + store: ReducerStore, +) { + return createCapability(source, store, source.continueRun); +} + const WEATHER_SPEC = { name: 'get_weather', description: 'Returns current weather for a location.', @@ -147,7 +154,7 @@ describe('createClientToolsCapability', () => { expect(msg.content).toBe('plain string'); }); - it('resolve(ok) calls source.runAgent with tools attached', async () => { + it('resolve(ok) requests continuation through the injected port', async () => { const source = makeSource(); const store = makeStore(); const cap = createClientToolsCapability(source, store); @@ -159,10 +166,7 @@ describe('createClientToolsCapability', () => { // Allow the void promise to flush await Promise.resolve(); - expect(source.runAgent).toHaveBeenCalledOnce(); - const args = source.runAgent.mock.calls[0][0] as { tools: unknown[] }; - expect(args.tools).toHaveLength(1); - expect((args.tools[0] as { name: string }).name).toBe('get_weather'); + expect(source.continueRun).toHaveBeenCalledOnce(); }); it('resolve(ok) drops the id from pending() and writes the result onto the store tool call', () => { @@ -216,7 +220,7 @@ describe('createClientToolsCapability', () => { expect(cap.pending()).toHaveLength(0); expect(store.toolCalls().find((tc) => tc.id === 'c1')?.result).toEqual({ temp: 70 }); expect(source.addMessage).toHaveBeenCalledOnce(); - expect(source.runAgent).not.toHaveBeenCalled(); + expect(source.continueRun).not.toHaveBeenCalled(); }); it('settle plus resolve flushes multiple pending results in one run', async () => { @@ -235,7 +239,7 @@ describe('createClientToolsCapability', () => { await Promise.resolve(); expect(source.addMessage).toHaveBeenCalledTimes(2); - expect(source.runAgent).toHaveBeenCalledTimes(1); + expect(source.continueRun).toHaveBeenCalledTimes(1); expect(source.addMessage.mock.calls.map(([message]) => ( message as { toolCallId: string } ).toolCallId)).toEqual(['c1', 'c2']); @@ -271,7 +275,7 @@ describe('createClientToolsCapability', () => { expect(msg.content).toContain('boom'); }); - it('resolve(error) still calls source.runAgent', async () => { + it('resolve(error) still requests continuation', async () => { const source = makeSource(); const store = makeStore(); const cap = createClientToolsCapability(source, store); @@ -280,7 +284,7 @@ describe('createClientToolsCapability', () => { cap.resolve('c2', { ok: false, error: 'network timeout' }); await Promise.resolve(); - expect(source.runAgent).toHaveBeenCalledOnce(); + expect(source.continueRun).toHaveBeenCalledOnce(); }); }); diff --git a/libs/ag-ui/src/lib/client-tools.ts b/libs/ag-ui/src/lib/client-tools.ts index 25d489d1f..0302a000a 100644 --- a/libs/ag-ui/src/lib/client-tools.ts +++ b/libs/ag-ui/src/lib/client-tools.ts @@ -16,9 +16,10 @@ import type { ReducerStore } from './reducer'; */ export interface ClientToolsSource { addMessage(message: Parameters[0]): void; - runAgent(parameters?: { tools?: Tool[] }): Promise; } +export type ContinueClientToolRun = () => Promise; + /** Convert a ClientToolSpec to the AG-UI Tool wire shape. */ function toAgUiTool(spec: ClientToolSpec): Tool { return { name: spec.name, description: spec.description, parameters: spec.parameters }; @@ -45,16 +46,17 @@ function safeStringify(v: unknown): string { * ask component re-renders with its emitted value as props and can branch to * a frozen state), and adds a ToolMessage via source.addMessage without * starting a run. - * - resolve(id, result): settles the result, then re-runs the agent with the - * catalog tools attached. Any ToolMessages previously settled into the - * source are flushed by that single run. + * - resolve(id, result): settles the result, then requests a continuation + * through the adapter-owned run gateway. Any ToolMessages previously + * settled into the source are flushed by that single run. * * Call catalogAsAgUiTools() to get the current catalog as AG-UI Tool[] for - * threading into runAgent(). + * attaching to each adapter-owned run. */ export function createClientToolsCapability( source: ClientToolsSource, store: ReducerStore, + continueRun: ContinueClientToolRun, ): ClientToolsCapability & { catalogAsAgUiTools(): Tool[] } { const catalog = signal([]); const resolvedIds = signal>(new Set()); @@ -126,7 +128,7 @@ export function createClientToolsCapability( resolve(id: string, result: ClientToolResult): void { settleResult(id, result); - void source.runAgent({ tools: catalogAsAgUiTools() }); + void continueRun(); }, catalogAsAgUiTools, diff --git a/libs/ag-ui/src/lib/to-agent.spec.ts b/libs/ag-ui/src/lib/to-agent.spec.ts index f8060e3c8..c2fe16dc2 100644 --- a/libs/ag-ui/src/lib/to-agent.spec.ts +++ b/libs/ag-ui/src/lib/to-agent.spec.ts @@ -356,6 +356,62 @@ describe('toAgent', () => { }); describe('message delivery lifecycle', () => { + it('owns assistant messages emitted by a client-tool continuation run', async () => { + const source = new StubAgent(); + const firstRun = deferNextRun(source); + const agent = toAgent(source as never); + agent.clientTools.setCatalog([{ + name: 'confirm_action', + description: 'Confirm an action.', + parameters: { type: 'object', properties: {} }, + }]); + + const firstPending = agent.submit({ message: 'start' }); + source.emit({ type: 'RUN_STARTED' } as BaseEvent, 'initial-run'); + source.emit({ + type: 'TOOL_CALL_START', + toolCallId: 'confirmation-1', + toolCallName: 'confirm_action', + parentMessageId: 'tool-parent', + } as never, 'initial-run'); + source.emit({ + type: 'TOOL_CALL_ARGS', + toolCallId: 'confirmation-1', + delta: '{}', + } as never, 'initial-run'); + source.emit({ type: 'TOOL_CALL_END', toolCallId: 'confirmation-1' } as never, 'initial-run'); + source.emit({ type: 'RUN_FINISHED' } as BaseEvent, 'initial-run'); + firstRun.resolve(); + await firstPending; + + expect(agent.clientTools.pending().map(call => call.id)).toEqual(['confirmation-1']); + + const continuationRun = deferNextRun(source); + agent.clientTools.resolve('confirmation-1', { ok: true, value: { confirmed: true } }); + source.emit({ type: 'RUN_STARTED' } as BaseEvent, 'continuation-run'); + source.emit({ + type: 'TEXT_MESSAGE_START', + messageId: 'continuation-answer', + role: 'assistant', + } as never, 'continuation-run'); + source.emit({ + type: 'TEXT_MESSAGE_CONTENT', + messageId: 'continuation-answer', + delta: 'Confirmed.', + } as never, 'continuation-run'); + + const continuedMessage = agent.messages().find(message => message.id === 'continuation-answer'); + expect(continuedMessage?.content).toBe('Confirmed.'); + expect(continuedMessage?.delivery.phase).toBe('streaming'); + + source.emit({ type: 'RUN_FINISHED' } as BaseEvent, 'continuation-run'); + continuationRun.resolve(); + await Promise.resolve(); + + expect(agent.messages().find(message => message.id === 'continuation-answer')?.delivery) + .toEqual(completeDelivery(continuedMessage!.delivery.generation, 'success')); + }); + it.each(['submit', 'retry', 'resume', 'regenerate'] as const)( '%s finalizes streamed messages as interrupted when transport resolves without a terminal event', async (operation) => { diff --git a/libs/ag-ui/src/lib/to-agent.ts b/libs/ag-ui/src/lib/to-agent.ts index d8c19f2b1..aa0017e60 100644 --- a/libs/ag-ui/src/lib/to-agent.ts +++ b/libs/ag-ui/src/lib/to-agent.ts @@ -152,10 +152,6 @@ export function toAgent(source: AbstractAgent, options: ToAgentOptions = {}): Ag return activeRun; } - // Build the client-tools capability. catalogAsAgUiTools() is used below to - // thread the catalog into every runAgent() call. - const clientToolsCap = createClientToolsCapability(source, store); - /** Forward a neutral-contract state patch onto the AG-UI run input. * Mirrors the canonical demo's `input.state` mechanism: the patch is * merged into the source agent's client state (carried on @@ -244,16 +240,20 @@ export function toAgent(source: AbstractAgent, options: ToAgentOptions = {}): Ag finishRunTelemetry(run); } - /** - * Fires the current message list against the source agent (no append). - * Both submit() and retry() share this path; submit() appends the user - * message first, retry() skips the append and calls this directly. - */ - async function runCurrentMessages(requestType = 'submit', allowBaselineTail = false): Promise { + type RunParameters = Parameters[0]; + + async function executeRun( + requestType: string, + parameters?: RunParameters, + allowBaselineTail = false, + ): Promise { const run = beginRun(requestType, allowBaselineTail); const tools = clientToolsCap.catalogAsAgUiTools(); + const runParameters = parameters === undefined && tools.length === 0 + ? undefined + : { ...parameters, ...(tools.length > 0 ? { tools } : {}) }; try { - await source.runAgent(tools.length > 0 ? { tools } : undefined); + await source.runAgent(runParameters); settleTransportClose(run); } catch (err) { if (run.outcome === 'aborted' && isAbortError(err)) return; @@ -261,6 +261,12 @@ export function toAgent(source: AbstractAgent, options: ToAgentOptions = {}): Ag } } + const clientToolsCap = createClientToolsCapability( + source, + store, + () => executeRun('client-tool-continuation', undefined, true), + ); + // Tap all events from the source agent via the AgentSubscriber API. // This subscription lives for the lifetime of `source`. source.subscribe({ @@ -379,18 +385,11 @@ export function toAgent(source: AbstractAgent, options: ToAgentOptions = {}): Ag // forwardedProps.command.resume mechanism. applyStatePatch(input.state); store.interrupt.set(undefined); - const run = beginRun('resume', true); - const tools = clientToolsCap.catalogAsAgUiTools(); - try { - await source.runAgent({ - forwardedProps: { command: { resume: input.resume } }, - ...(tools.length > 0 ? { tools } : {}), - }); - settleTransportClose(run); - } catch (err) { - if (run.outcome === 'aborted' && isAbortError(err)) return; - failRun(run, err); - } + await executeRun( + 'resume', + { forwardedProps: { command: { resume: input.resume } } }, + true, + ); return; } @@ -409,7 +408,7 @@ export function toAgent(source: AbstractAgent, options: ToAgentOptions = {}): Ag // user message (the message is already in the list by this point). lastInput = input; - await runCurrentMessages(); + await executeRun('submit'); }, retry: async () => { @@ -419,7 +418,7 @@ export function toAgent(source: AbstractAgent, options: ToAgentOptions = {}): Ag // Re-run the same message list against the source without appending a // duplicate user message — the message is already in store.messages and // source's internal list from the original submit(). - await runCurrentMessages('retry', true); + await executeRun('retry', undefined, true); }, stop: async () => { @@ -467,15 +466,7 @@ export function toAgent(source: AbstractAgent, options: ToAgentOptions = {}): Ag // message in `trimmed` becomes the active prompt for the next run. source.setMessages(trimmed as Parameters[0]); - const run = beginRun('regenerate'); - const regenTools = clientToolsCap.catalogAsAgUiTools(); - try { - await source.runAgent(regenTools.length > 0 ? { tools: regenTools } : undefined); - settleTransportClose(run); - } catch (err) { - if (run.outcome === 'aborted' && isAbortError(err)) return; - failRun(run, err); - } + await executeRun('regenerate'); }, }; } diff --git a/libs/chat/src/lib/agent/agent-ref.type-spec.ts b/libs/chat/src/lib/agent/agent-ref.type-spec.ts index 89865893d..b1e39f133 100644 --- a/libs/chat/src/lib/agent/agent-ref.type-spec.ts +++ b/libs/chat/src/lib/agent/agent-ref.type-spec.ts @@ -11,4 +11,4 @@ type _refTyped = Expect>>; type _tokenTyped = Expect>>>; // default state when no param. -type _default = Expect>>>; +type _default = Expect>>; diff --git a/libs/chat/src/lib/agent/agent-with-history.ts b/libs/chat/src/lib/agent/agent-with-history.ts index be44d7a15..c0826df47 100644 --- a/libs/chat/src/lib/agent/agent-with-history.ts +++ b/libs/chat/src/lib/agent/agent-with-history.ts @@ -10,7 +10,7 @@ import type { AgentCheckpoint } from './agent-checkpoint'; * implement this. Pure request/response runtimes that don't have checkpoints * should implement plain Agent. */ -export interface AgentWithHistory> extends Agent { +export interface AgentWithHistory extends Agent { history: Signal; /** * Optional reactive map of `messageId → checkpointId`, computed by diff --git a/libs/chat/src/lib/agent/agent.ts b/libs/chat/src/lib/agent/agent.ts index af7e8033e..aad10f1d7 100644 --- a/libs/chat/src/lib/agent/agent.ts +++ b/libs/chat/src/lib/agent/agent.ts @@ -24,7 +24,7 @@ import type { AgentError } from './agent-error'; * Invariant: state lives on signals; `events$` carries only things that are * not derivable from signals. */ -export interface Agent> { +export interface Agent { // Core state messages: Signal; status: Signal; diff --git a/libs/chat/src/lib/compositions/chat/chat.component.spec.ts b/libs/chat/src/lib/compositions/chat/chat.component.spec.ts index 937a914f2..97378355a 100644 --- a/libs/chat/src/lib/compositions/chat/chat.component.spec.ts +++ b/libs/chat/src/lib/compositions/chat/chat.component.spec.ts @@ -15,6 +15,7 @@ import { createA2uiSurfaceStore } from '../../a2ui/surface-store'; import { signalStateStore, toRenderRegistry, views } from '@threadplane/render'; import { a2uiBasicCatalog } from '../../a2ui/catalog/index'; import { ChatGenerativeUiComponent } from '../../primitives/chat-generative-ui/chat-generative-ui.component'; +import { ChatMessageComponent } from '../../primitives/chat-message/chat-message.component'; import { ChatStreamingMdComponent, markdownDocument } from '../../streaming/streaming-markdown.component'; import type { Spec, StateStore } from '@json-render/core'; import type { AgentEvent } from '../../agent/agent-event'; @@ -846,6 +847,32 @@ describe('ChatComponent — markdown delivery', () => { TestBed.configureTestingModule({ imports: [ChatComponent] }); }); + it.each([ + { + name: 'streams when message delivery is streaming even while the agent is not loading', + delivery: streamingDelivery('answer-attempt'), + isLoading: false, + expected: true, + }, + { + name: 'does not stream when message delivery is complete even while the agent is loading', + delivery: completeDelivery('answer-attempt', 'success'), + isLoading: true, + expected: false, + }, + ])('$name', ({ delivery, isLoading, expected }) => { + const agent = mockAgent({ + messages: [{ id: 'a1', role: 'assistant', content: 'answer', delivery }], + isLoading, + }); + const fixture = TestBed.createComponent(ChatComponent); + fixture.componentRef.setInput('agent', agent); + fixture.detectChanges(); + + const message = fixture.debugElement.query(By.directive(ChatMessageComponent)); + expect(message.componentInstance.streaming()).toBe(expected); + }); + it('binds the classified markdown document to the message delivery', () => { const delivery = streamingDelivery('answer-attempt'); const agent = mockAgent({ diff --git a/libs/chat/src/lib/compositions/chat/chat.component.ts b/libs/chat/src/lib/compositions/chat/chat.component.ts index 3026b2cb6..9cef42afc 100644 --- a/libs/chat/src/lib/compositions/chat/chat.component.ts +++ b/libs/chat/src/lib/compositions/chat/chat.component.ts @@ -199,12 +199,11 @@ export function isPinned( @let content = messageContent(message); @let classified = classifyMessage(content, message); - @let pending = classified.type() === 'pending';