Skip to content

Commit c870088

Browse files
committed
feat(angular): upgrade Angular renderer to v0.9 dynamic architecture
- Major Architecture Upgrade: Transitioned from static components per element (v0.8) to a fully dynamic component hosting paradigm (v0.9) with dynamic allocation and decoupled bindings (`ComponentHostComponent`). - V0.8 Isolation: Extracted and locked existing legacy configurations into `/src/lib/v0_8/` workspaces backing backwards compatibility layout managers securely. - Unified Inspector Client Dashboard: Set up a custom workspace host system dashboard (`/demo-app`) serving standalone layout widgets pipelines containing hot-reactive aggregates views. - schemas and execution aligning updates: Fixed `BASIC_FUNCTIONS` aligning schemas and validation designs layout. - License compliance instructions: Fixed `.github/workflows/check_license.yml` to help describe local license remediation diagnostics correctly rather than just failing triggers.
1 parent 98fc29a commit c870088

114 files changed

Lines changed: 16711 additions & 297 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/check_license.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ jobs:
3838

3939
- name: Check license headers
4040
run: |
41-
addlicense -check \
41+
if ! addlicense -check \
4242
-l apache \
4343
-c "Google LLC" \
44-
.
44+
.; then
45+
echo "License check failed. To fix this, install addlicense and run it:"
46+
echo " go install github.com/google/addlicense@latest"
47+
echo " addlicense -l apache -c \"Google LLC\" ."
48+
exit 1
49+
fi

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ site/
1515

1616
# Python virtual environment
1717
.venv/
18+
coverage/
1819

1920
# Generated spec assets in the agent SDK
2021
## old agent SDK path

renderers/angular/.npmrc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
@a2ui:registry=https://us-npm.pkg.dev/oss-exit-gate-prod/a2ui--npm/
2-
//us-npm.pkg.dev/oss-exit-gate-prod/a2ui--npm/:always-auth=true

renderers/angular/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
2+
## 0.9
3+
4+
- Implement renderer for v0.9 of A2UI.
5+
16
## 0.8.5
27

38
- Handle `TextField.type` renamed to `TextField.textFieldType`.

renderers/angular/README.md

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,118 @@
1-
Angular implementation of A2UI.
1+
# A2UI Angular Renderer
22

3-
Important: The sample code provided is for demonstration purposes and illustrates the mechanics of A2UI and the Agent-to-Agent (A2A) protocol. When building production applications, it is critical to treat any agent operating outside of your direct control as a potentially untrusted entity.
3+
The `@a2ui/angular` package provides the Angular implementation for rendering A2UI surfaces, bridging the Agent-to-Agent (A2A) protocol to Angular-friendly components.
4+
5+
## Architecture & Versions
6+
7+
The package contains evolving architectures to support different A2UI specification versions:
8+
9+
- **`v0_8`**: Initial approach utilizing dedicated, static Angular components for each element type (e.g., `<a2ui-button>`).
10+
- **`v0_9`**: Dynamic approach centering around a single generic host component (`ComponentHostComponent`) coupled with extensible `Catalog` registries. **This is the recommended architecture for modern integrations.**
11+
12+
---
13+
14+
## Getting Started (`v0_9`)
15+
16+
The `v0_9` model decouples rendering mechanics from static templates by binding model state dynamically through dynamic component allocation.
17+
18+
### 1. Register Components in a Catalog
19+
20+
Extend `AngularCatalog` or use preset catalogs like `MinimalCatalog`. Define your custom elements using a dynamic mapping:
21+
22+
```typescript
23+
import { Injectable } from '@angular/core';
24+
import { MinimalCatalog } from '@a2ui/angular/lib/v0_9/catalog/minimal/minimal-catalog';
25+
import { CustomComponent } from './custom-component';
26+
27+
@Injectable({ providedIn: 'root' })
28+
export class MyCatalog extends MinimalCatalog {
29+
constructor() {
30+
super();
31+
32+
const customApi = {
33+
name: 'CustomComponent',
34+
schema: { ... }, // Zod schema spec
35+
component: CustomComponent,
36+
};
37+
38+
const components = Array.from(this.components.values());
39+
components.push(customApi);
40+
41+
// Merge custom registrations
42+
(this as any).components = new Map(components.map((c) => [c.name, c]));
43+
}
44+
}
45+
```
46+
47+
### 2. Provide Renderer Infrastructure
48+
49+
In your dashboard component or module providers tier, initialize the `A2uiRendererService` and declare the backing `AngularCatalog`:
50+
51+
```typescript
52+
import { Component, inject, OnInit } from '@angular/core';
53+
import { A2uiRendererService } from '@a2ui/angular/lib/v0_9/core/a2ui-renderer.service';
54+
import { AngularCatalog } from '@a2ui/angular/lib/v0_9/catalog/types';
55+
import { MyCatalog } from './my-catalog';
56+
57+
@Component({
58+
selector: 'app-dashboard',
59+
providers: [
60+
A2uiRendererService,
61+
{ provide: AngularCatalog, useClass: MyCatalog },
62+
]
63+
})
64+
```
65+
66+
### 3. Initialize Layout and Render
67+
68+
Prepare the service on load and supply the generic host targeting the source surface:
69+
70+
```typescript
71+
export class DashboardComponent implements OnInit {
72+
private rendererService = inject(A2uiRendererService);
73+
surfaceId = 'dashboard-surface';
74+
75+
ngOnInit() {
76+
this.rendererService.initialize();
77+
}
78+
79+
onMessagesReceived(messages: any[]) {
80+
this.rendererService.processMessages(messages);
81+
}
82+
}
83+
```
84+
85+
Place the `<a2ui-v09-component-host>` component in your template pointing to the desired layout node:
86+
87+
```html
88+
<a2ui-v09-component-host [surfaceId]="surfaceId" componentId="root">
89+
</a2ui-v09-component-host>
90+
```
91+
92+
---
93+
94+
## Building and Development
95+
96+
### Building the Package
97+
Distributes the library bundle utilizing `ng-packagr` outputting to `./dist`:
98+
```bash
99+
npm run build
100+
```
101+
102+
### Running the Demo
103+
Starts a dev environment rendering local samples containing live inspectors reviewing data pipelines:
104+
```bash
105+
npm run demo
106+
```
107+
108+
---
109+
110+
## Legal Notice
111+
112+
**Important**: The sample code provided is for demonstration purposes and illustrates the mechanics of A2UI and the Agent-to-Agent (A2A) protocol. When building production applications, it is critical to treat any agent operating outside of your direct control as a potentially untrusted entity.
4113

5114
All operational data received from an external agent—including its AgentCard, messages, artifacts, and task statuses—should be handled as untrusted input. For example, a malicious agent could provide crafted data in its fields (e.g., name, skills.description) that, if used without sanitization to construct prompts for a Large Language Model (LLM), could expose your application to prompt injection attacks.
6115

7116
Similarly, any UI definition or data stream received must be treated as untrusted. Malicious agents could attempt to spoof legitimate interfaces to deceive users (phishing), inject malicious scripts via property values (XSS), or generate excessive layout complexity to degrade client performance (DoS). If your application supports optional embedded content (such as iframes or web views), additional care must be taken to prevent exposure to malicious external sites.
8117

9-
Developer Responsibility: Failure to properly validate data and strictly sandbox rendered content can introduce severe vulnerabilities. Developers are responsible for implementing appropriate security measures—such as input sanitization, Content Security Policies (CSP), strict isolation for optional embedded content, and secure credential handling—to protect their systems and users.
118+
**Developer Responsibility**: Failure to properly validate data and strictly sandbox rendered content can introduce severe vulnerabilities. Developers are responsible for implementing appropriate security measures—such as input sanitization, Content Security Policies (CSP), strict isolation for optional embedded content, and secure credential handling—to protect their systems and users.

renderers/angular/angular.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,67 @@
2727
}
2828
}
2929
}
30+
},
31+
"demo-app": {
32+
"projectType": "application",
33+
"schematics": {},
34+
"root": "demo-app",
35+
"sourceRoot": "demo-app/src",
36+
"prefix": "app",
37+
"architect": {
38+
"build": {
39+
"builder": "@angular/build:application",
40+
"options": {
41+
"browser": "demo-app/src/main.ts",
42+
"tsConfig": "demo-app/tsconfig.app.json",
43+
"assets": [
44+
{
45+
"glob": "**/*",
46+
"input": "demo-app/public"
47+
}
48+
],
49+
"styles": ["demo-app/src/styles.css"]
50+
},
51+
"configurations": {
52+
"production": {
53+
"budgets": [
54+
{
55+
"type": "initial",
56+
"maximumWarning": "500kB",
57+
"maximumError": "1MB"
58+
},
59+
{
60+
"type": "anyComponentStyle",
61+
"maximumWarning": "4kB",
62+
"maximumError": "8kB"
63+
}
64+
],
65+
"outputHashing": "all"
66+
},
67+
"development": {
68+
"optimization": false,
69+
"extractLicenses": false,
70+
"sourceMap": true
71+
}
72+
},
73+
"defaultConfiguration": "production"
74+
},
75+
"serve": {
76+
"builder": "@angular/build:dev-server",
77+
"configurations": {
78+
"production": {
79+
"buildTarget": "demo-app:build:production"
80+
},
81+
"development": {
82+
"buildTarget": "demo-app:build:development"
83+
}
84+
},
85+
"defaultConfiguration": "development"
86+
},
87+
"test": {
88+
"builder": "@angular/build:unit-test"
89+
}
90+
}
3091
}
3192
},
3293
"cli": {
14.7 KB
Binary file not shown.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { Injectable } from '@angular/core';
18+
import { A2uiRendererService } from '../../../src/lib/v0_9/core/a2ui-renderer.service';
19+
20+
/**
21+
* A stub service that simulates an A2UI agent.
22+
* It listens for actions and responds with data model updates or new surfaces.
23+
*/
24+
@Injectable({
25+
providedIn: 'root',
26+
})
27+
export class AgentStubService {
28+
/** Log of actions received from the surface. */
29+
actionsLog: any[] = [];
30+
31+
constructor(private rendererService: A2uiRendererService) {}
32+
33+
/**
34+
* Pushes actions triggered from the rendered Canvas frame through simulation.
35+
* - Logs actions into inspector event frame aggregates.
36+
* - Emulates generic server-side evaluation triggers delaying deferred updates.
37+
* - Dispatch subsequent node-tree node triggers back over `A2uiRendererService`.
38+
*/
39+
handleAction(action: any) {
40+
console.log('[AgentStub] handleAction action:', action);
41+
this.actionsLog.push({ timestamp: new Date(), action });
42+
43+
// Simulate server processing delay
44+
setTimeout(() => {
45+
if (action.event?.name === 'update_property') {
46+
const { path, value, surfaceId } = action.event.context;
47+
console.log('[AgentStub] update_property path:', path, 'value:', value, 'surfaceId:', surfaceId);
48+
this.rendererService.processMessages([
49+
{
50+
version: 'v0.9',
51+
updateDataModel: {
52+
surfaceId: surfaceId || action.surfaceId,
53+
path: path,
54+
value: value,
55+
},
56+
},
57+
]);
58+
} else if (action.event?.name === 'submit_form') {
59+
const formData = action.event.context;
60+
const name = formData.name || 'Anonymous';
61+
62+
// Respond with an update to the data model in v0.9 layout
63+
this.rendererService.processMessages([
64+
{
65+
version: 'v0.9',
66+
updateDataModel: {
67+
surfaceId: action.surfaceId,
68+
path: '/form/submitted',
69+
value: true,
70+
},
71+
},
72+
{
73+
version: 'v0.9',
74+
updateDataModel: {
75+
surfaceId: action.surfaceId,
76+
path: '/form/responseMessage',
77+
value: `Hello, ${name}! Your form has been processed.`,
78+
},
79+
},
80+
]);
81+
}
82+
}, 50); // Shorter delay for property updates
83+
}
84+
85+
/**
86+
* Initializes a demo session with an initial set of messages.
87+
*/
88+
initializeDemo(initialMessages: any[]) {
89+
this.rendererService.initialize((action) => this.handleAction(action));
90+
this.rendererService.processMessages(initialMessages);
91+
}
92+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
18+
19+
export const appConfig: ApplicationConfig = {
20+
providers: [provideBrowserGlobalErrorListeners()],
21+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
18+
import { App } from './app';
19+
20+
describe('App', () => {
21+
beforeEach(async () => {
22+
await TestBed.configureTestingModule({
23+
imports: [App],
24+
}).compileComponents();
25+
});
26+
27+
it('should create the app', () => {
28+
const fixture = TestBed.createComponent(App);
29+
const app = fixture.componentInstance;
30+
expect(app).toBeTruthy();
31+
32+
fixture.detectChanges(); // Trigger ngOnInit
33+
34+
const compiled = fixture.nativeElement as HTMLElement;
35+
expect(compiled.querySelector('.canvas-frame')).toBeTruthy();
36+
});
37+
38+
it('should render title', () => {
39+
const fixture = TestBed.createComponent(App);
40+
fixture.detectChanges();
41+
42+
const compiled = fixture.nativeElement as HTMLElement;
43+
expect(compiled.querySelector('h3')?.textContent).toContain('A2UI Examples');
44+
});
45+
});

0 commit comments

Comments
 (0)