Skip to content

Commit 01bbacf

Browse files
bloveclaude
andcommitted
feat(cockpit): Microsoft Agent Framework runtime example (cockpit/runtimes)
First entry on the one-capability-many-runtimes axis: an expense-approval example whose backend is genuinely non-LangGraph (agent-framework behind the agent-framework-ag-ui bridge) while the Angular side is the same neutral-contract UI code every AG-UI example uses (provideAgent/injectAgent from @threadplane/ag-ui + @threadplane/chat primitives). Surfaces demonstrated (per the measured 2026-08-31 runtime matrix): - messages: streamed assistant text - tool calls: lookup_expense_policy executes server-side - shared state: predict_state_config streams the submit_expense argument into frontend state (STATE_SNAPSHOT/STATE_DELTA) predictively - interrupts: submit_expense has approval_mode='always_require'; the run finishes with the protocol-standard RUN_FINISHED interrupt outcome and resumes from the client's top-level resume entries — the first real proof of #888/#889/#891 outside unit tests (verified live end-to-end on the OpenAI path through the served Angular app) - subagents: not demonstrated (bridge emits no ACTIVITY stream; measured red upstream) Model client: Azure OpenAI is the DEFAULT (AZURE_OPENAI_ENDPOINT/ API_KEY/MODEL/API_VERSION, key auth — structurally verified only, no Azure credentials were available), falling back to the plain OpenAI client on OPENAI_API_KEY (live-smoked; also the aimock e2e path via OPENAI_BASE_URL). Both live in agent-framework-openai — no extra Azure package needed. Wiring: registry entry rt-maf (product 'runtimes', framework 'microsoft-agent-framework', ports 4330/5330), cockpit/ports.mjs, smoke project in ci.yml, deploy-ag-ui path trigger for cockpit/runtimes, and regenerated deployments/ag-ui-dev (server.py mounts the agent via add_agent_framework_fastapi_endpoint; requirements.txt gains agent-framework-ag-ui==1.2.1 / core==1.16.0 / openai==1.14.1 via the uv-export direct-deps mechanism; combined set uv-pip-compile resolves). e2e: aimock fixture replay (hasToolResult entries precede the plain userMessage entry; turnIndex disambiguates the two post-tool calls); 3 specs green. Prod build green (warning-only budget). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent d8628d3 commit 01bbacf

52 files changed

Lines changed: 3326 additions & 9 deletions

Some content is hidden

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

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ jobs:
252252
node-version: 22
253253
cache: npm
254254
- run: npm ci
255-
- run: npx nx run-many -t smoke --projects=cockpit-ag-ui-interrupts-python,cockpit-ag-ui-streaming-python,cockpit-deep-agents-planning-python,cockpit-deep-agents-filesystem-python,cockpit-deep-agents-subagents-python,cockpit-deep-agents-memory-python,cockpit-deep-agents-skills-python,cockpit-deep-agents-sandboxes-python,cockpit-langgraph-persistence-python,cockpit-langgraph-durable-execution-python,cockpit-langgraph-streaming-python,cockpit-langgraph-interrupts-python,cockpit-langgraph-memory-python,cockpit-langgraph-subgraphs-python,cockpit-langgraph-time-travel-python,cockpit-langgraph-deployment-runtime-python,cockpit-chat-messages-python,cockpit-render-spec-rendering-python --skip-nx-cache
255+
- run: npx nx run-many -t smoke --projects=cockpit-ag-ui-interrupts-python,cockpit-ag-ui-streaming-python,cockpit-deep-agents-planning-python,cockpit-deep-agents-filesystem-python,cockpit-deep-agents-subagents-python,cockpit-deep-agents-memory-python,cockpit-deep-agents-skills-python,cockpit-deep-agents-sandboxes-python,cockpit-langgraph-persistence-python,cockpit-langgraph-durable-execution-python,cockpit-langgraph-streaming-python,cockpit-langgraph-interrupts-python,cockpit-langgraph-memory-python,cockpit-langgraph-subgraphs-python,cockpit-langgraph-time-travel-python,cockpit-langgraph-deployment-runtime-python,cockpit-chat-messages-python,cockpit-render-spec-rendering-python,cockpit-runtimes-microsoft-agent-framework-python --skip-nx-cache
256256

257257
cockpit-deploy-smoke:
258258
name: Cockpit — deploy smoke dry-run

.github/workflows/deploy-ag-ui.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
branches: [main]
66
paths:
77
- 'cockpit/ag-ui/**/python/**'
8+
- 'cockpit/runtimes/**/python/**'
89
- 'apps/cockpit/scripts/capability-registry.ts'
910
- 'scripts/generate-ag-ui-deployment-config.ts'
1011
- 'deployments/ag-ui-dev/**'

apps/cockpit/scripts/capability-registry.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ export const capabilities: readonly Capability[] = [
7676
{ id: 'ag-ui-client-tools', product: 'ag-ui', topic: 'client-tools', angularProject: 'cockpit-ag-ui-client-tools-angular', port: 4325, pythonPort: 5325, pythonDir: 'cockpit/ag-ui/client-tools/python' },
7777
{ id: 'ag-ui-a2ui', product: 'ag-ui', topic: 'a2ui', angularProject: 'cockpit-ag-ui-a2ui-angular', port: 4324, pythonPort: 5324, pythonDir: 'cockpit/ag-ui/a2ui/python' },
7878
{ id: 'ag-ui-subagents', product: 'ag-ui', topic: 'subagents', angularProject: 'cockpit-ag-ui-subagents-angular', port: 4326, pythonPort: 5326, pythonDir: 'cockpit/ag-ui/subagents/python' },
79+
// Runtime-portability examples (one capability, many runtimes; AG-UI-served
80+
// like the ag-ui caps, but the backend is genuinely non-LangGraph)
81+
{ id: 'rt-maf', product: 'runtimes', topic: 'microsoft-agent-framework', angularProject: 'cockpit-runtimes-microsoft-agent-framework-angular', port: 4330, pythonPort: 5330, pythonDir: 'cockpit/runtimes/microsoft-agent-framework/python', framework: 'microsoft-agent-framework' },
7982
] as const;
8083

8184
export function findCapability(id: string): Capability | undefined {

cockpit/ports.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const PORTS = Object.freeze({
3232
'cockpit-ag-ui-a2ui-angular': { angular: 4324, langgraph: 5324 },
3333
'cockpit-ag-ui-client-tools-angular': { angular: 4325, langgraph: 5325 },
3434
'cockpit-ag-ui-subagents-angular': { angular: 4326, langgraph: 5326 },
35+
'cockpit-runtimes-microsoft-agent-framework-angular': { angular: 4330, langgraph: 5330 },
3536
'cockpit-chat-a2ui-angular': { angular: 4511, langgraph: 5511 },
3637
'cockpit-chat-debug-angular': { angular: 4509, langgraph: 5509 },
3738
'cockpit-chat-generative-ui-angular': { angular: 4508, langgraph: 5508 },
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": { "userMessage": "team dinner", "hasToolResult": true, "turnIndex": 2 },
5+
"response": {
6+
"content": "Your $220.00 expense to Blue Finch Bistro has been submitted for reimbursement."
7+
}
8+
},
9+
{
10+
"match": { "userMessage": "team dinner", "hasToolResult": true, "turnIndex": 1 },
11+
"response": {
12+
"toolCalls": [
13+
{
14+
"name": "submit_expense",
15+
"arguments": {
16+
"expense": {
17+
"vendor": "Blue Finch Bistro",
18+
"category": "meals",
19+
"amount_usd": 220,
20+
"memo": "Platform team offsite dinner, 6 attendees"
21+
}
22+
}
23+
}
24+
]
25+
}
26+
},
27+
{
28+
"match": { "userMessage": "team dinner" },
29+
"response": {
30+
"toolCalls": [
31+
{
32+
"name": "lookup_expense_policy",
33+
"arguments": { "category": "meals" }
34+
}
35+
]
36+
}
37+
}
38+
]
39+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: MIT
2+
import { resolve } from 'node:path';
3+
import { portsFor } from '../../../../../cockpit/ports.mjs';
4+
import { createAgUiGlobalSetup } from '@threadplane-internal/e2e-harness';
5+
6+
const ports = portsFor('cockpit-runtimes-microsoft-agent-framework-angular');
7+
8+
export default createAgUiGlobalSetup({
9+
pythonCwd: 'cockpit/runtimes/microsoft-agent-framework/python',
10+
backendPort: ports.langgraph,
11+
angularProject: 'cockpit-runtimes-microsoft-agent-framework-angular',
12+
angularPort: ports.angular,
13+
fixturesDir: resolve(__dirname, 'fixtures'),
14+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// SPDX-License-Identifier: MIT
2+
import { test, expect } from '@playwright/test';
3+
4+
// First proof outside unit tests that the neutral Agent contract's interrupt
5+
// path works against a genuinely non-LangGraph AG-UI backend: the Microsoft
6+
// Agent Framework bridge signals interrupts via the protocol-standard
7+
// RUN_FINISHED outcome (never CUSTOM on_interrupt), and resume goes out as
8+
// the top-level `resume` entry array.
9+
test.describe('cockpit runtimes/microsoft-agent-framework: expense approval', () => {
10+
test('approval card displays the pending submit_expense call', async ({ page }) => {
11+
await page.goto('/');
12+
await page.getByText('File a team dinner expense').click();
13+
const dialog = page.locator('dialog.chat-approval-card');
14+
await expect(dialog).toBeVisible({ timeout: 30_000 });
15+
await expect(dialog).toContainText('Expense approval required');
16+
await expect(dialog).toContainText('Blue Finch Bistro');
17+
await expect(dialog).toContainText('meals');
18+
});
19+
20+
test('shared state panel mirrors the drafted expense', async ({ page }) => {
21+
await page.goto('/');
22+
await page.getByText('File a team dinner expense').click();
23+
const panel = page.getByTestId('expense-state');
24+
await expect(panel).toContainText('Blue Finch Bistro', { timeout: 30_000 });
25+
await expect(panel).toContainText('$220.00');
26+
});
27+
28+
test('Approve resumes the run and the expense is submitted', async ({ page }) => {
29+
await page.goto('/');
30+
await page.getByText('File a team dinner expense').click();
31+
const dialog = page.locator('dialog.chat-approval-card');
32+
await expect(dialog).toBeVisible({ timeout: 30_000 });
33+
await dialog.getByRole('button', { name: 'Approve' }).click();
34+
await expect(page.getByText(/has been submitted for reimbursement/i)).toBeVisible({ timeout: 30_000 });
35+
});
36+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: MIT
2+
import { defineConfig, devices } from '@playwright/test';
3+
import { portsFor } from '../../../../../cockpit/ports.mjs';
4+
5+
const { angular: angularPort } = portsFor('cockpit-runtimes-microsoft-agent-framework-angular');
6+
7+
8+
export default defineConfig({
9+
testDir: '.',
10+
testMatch: '**/*.spec.ts',
11+
fullyParallel: false,
12+
workers: 1,
13+
retries: process.env.CI ? 2 : 0,
14+
reporter: process.env.CI ? [['list'], ['html', { open: 'never' }]] : 'list',
15+
use: {
16+
baseURL: `http://localhost:${angularPort}`,
17+
trace: 'retain-on-failure',
18+
},
19+
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
20+
globalSetup: './global-setup-impl.ts',
21+
globalTeardown: require.resolve('../../../../../libs/e2e-harness/src/global-teardown'),
22+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"module": "ES2022",
5+
"moduleResolution": "Bundler",
6+
"esModuleInterop": true,
7+
"strict": true,
8+
"skipLibCheck": true,
9+
"noEmit": true,
10+
"types": [
11+
"node"
12+
],
13+
"baseUrl": "../../../../..",
14+
"paths": {
15+
"@threadplane-internal/e2e-harness": [
16+
"libs/e2e-harness/src/index.ts"
17+
],
18+
"@threadplane-internal/e2e-harness/global-teardown": [
19+
"libs/e2e-harness/src/global-teardown.ts"
20+
]
21+
},
22+
"allowJs": true
23+
},
24+
"include": [
25+
"**/*.ts"
26+
],
27+
"exclude": [
28+
"node_modules",
29+
"test-results",
30+
"playwright-report"
31+
]
32+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "@threadplane/cockpit-runtimes-microsoft-agent-framework-angular",
3+
"private": true,
4+
"version": "0.0.1",
5+
"peerDependencies": {
6+
"@threadplane/chat": "*",
7+
"@threadplane/ag-ui": "*"
8+
},
9+
"license": "MIT",
10+
"sideEffects": false
11+
}

0 commit comments

Comments
 (0)