Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
90e12c3
feat(cockpit): complete cockpit application with 14 capability exampl…
blove Apr 5, 2026
5fa8e75
feat: migrate all domains from stream-resource.dev to cacheplane.ai
blove Apr 7, 2026
427bc73
docs: add streaming generative UI design spec and research
blove Apr 8, 2026
98134d2
docs: add streaming generative UI implementation plan
blove Apr 8, 2026
c94a973
chore: scaffold @cacheplane/partial-json library
blove Apr 8, 2026
93d09a5
feat(partial-json): add node types and parser interface
blove Apr 8, 2026
7456a4e
feat(partial-json): implement character-by-character streaming parser
blove Apr 8, 2026
8718b2a
feat(partial-json): add materialization with structural sharing
blove Apr 8, 2026
31d2ed6
perf(render): add element-level memoization via Object.is equality
blove Apr 8, 2026
b6691a8
feat(chat): add ParseTreeStore for streaming JSON-to-spec materializa…
blove Apr 8, 2026
2851bba
feat(chat): add ContentClassifier for streaming content type detection
blove Apr 8, 2026
a19acc8
feat(chat): integrate content classifier and generative UI rendering
blove Apr 8, 2026
ecfe7a7
fix(chat): add @cacheplane/partial-json peer dependency
blove Apr 8, 2026
e3f2328
docs: add streaming generative UI docs update design spec
blove Apr 8, 2026
546b059
docs: add streaming generative UI docs update implementation plan
blove Apr 8, 2026
23b71a2
refactor(chat): remove ChatGenerativeUiComponent from public API
blove Apr 8, 2026
1db1f26
docs(website): register streaming guide and API pages in nav config
blove Apr 8, 2026
feb5a79
docs(chat): remove ChatGenerativeUiComponent from introduction
blove Apr 8, 2026
1cc8021
docs(chat): rewrite generative UI guide for streaming auto-detection
blove Apr 8, 2026
be4be59
docs(chat): add streaming content classification guide
blove Apr 8, 2026
116b9c9
docs(chat): add ContentClassifier and ParseTreeStore API references
blove Apr 8, 2026
d395ce5
docs(chat): add views and store inputs to ChatComponent docs
blove Apr 8, 2026
7a6de16
docs(chat): remove renderRegistry from configuration and API docs
blove Apr 8, 2026
69e9b8f
docs(website): update landing page generative UI references
blove Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
"runtimeExecutable": "/bin/bash",
"runtimeArgs": ["-c", "export PATH=/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH && npx nx serve cockpit --port 4201"],
"port": 4201
},
{
"name": "streaming",
"runtimeExecutable": "/bin/bash",
"runtimeArgs": ["-c", "export PATH=/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH && npx nx serve cockpit-langgraph-streaming-angular --port 4300"],
"port": 4300
}
]
}
1 change: 1 addition & 0 deletions .claude/worktrees/blissful-bartik
Submodule blissful-bartik added at 44e21d
1 change: 1 addition & 0 deletions .claude/worktrees/optimistic-jang
Submodule optimistic-jang added at 0a6d25
1 change: 1 addition & 0 deletions .claude/worktrees/website-iteration
Submodule website-iteration added at 510bef
1 change: 1 addition & 0 deletions .claude/worktrees/zealous-jones
Submodule zealous-jones added at 413d3f
79 changes: 79 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Contributor Agent Guide

This file is for agents working in this repository. It is contributor-facing, not consumer-facing. Keep decisions grounded in the actual repo state, and prefer project-specific instructions over generic agent habits.

## Scope and Precedence

- This guide is for contributors working in the monorepo.
- Public-facing agent context lives under `apps/website/public/` and exists for package users, docs readers, and external tooling.
- When instructions conflict, prefer the most local project instruction available.
- Treat reference docs and copied workflows as inputs, not as truth. Verify against the codebase before acting.

## Operating Principles

- Do not bluff. If something is uncertain, say what is uncertain and verify it from code, docs, or tooling.
- Do the research yourself when the answer can be found locally or by using current documentation.
- Explore first, then edit. For non-trivial work, inspect the relevant files and form a plan before changing code.
- Use applicable workflow guidance before defaulting to generic implementation habits.
- Keep communication direct, calm, and specific. Avoid performative certainty, unnecessary filler, or aggressive phrasing.

## Planning and Execution

- For simple, localized changes, a brief mental plan is fine.
- For anything that spans multiple files, affects behavior, or has unclear boundaries, inspect the codebase and write out a plan before editing.
- Follow existing patterns unless there is a concrete reason to improve them as part of the task.
- Keep changes scoped to the goal. Do not fold unrelated refactors into the same task unless they are required to make the work coherent.
- If you discover the current approach is wrong, adjust course explicitly instead of forcing the original plan through.

## Commands and Tooling

- Prefer setting the tool's working directory over shell patterns like `cd path && command`. In Codex, pass `workdir` directly to the command tool.
- Use the repo's actual package manager and task runner. This repo uses `npm` at the root and `nx` for workspace tasks.
- Prefer `rg` and `rg --files` for search.
- Prefer non-interactive commands.
- Avoid destructive git operations unless explicitly requested.
- Do not substitute other runners when the repo already defines the right command.

## Repo Layout

- `libs/stream-resource`: main Angular library.
- `apps/website`: docs and marketing site.
- `packages/mcp`: MCP server package.
- `e2e/stream-resource-e2e`: end-to-end coverage for the workspace.
- `apps/demo` and `apps/demo-e2e`: demo application and related end-to-end coverage.

## Working in This Repo

- The workspace is Nx-based. Prefer project-scoped commands over broad workspace runs unless the task actually needs broader verification.
- Inspect `project.json`, `nx.json`, and existing scripts before inventing commands.
- If you need Nx-specific syntax or behavior and it is not obvious from local config, verify it from current Nx docs rather than relying on memory.
- Respect generated and public-facing context files. If the task changes docs, API surface, positioning, or package guidance, check whether agent context or docs should be regenerated.

## Docs and Generated Context

- Do not commit generated plans, analyses, or reports unless explicitly requested.
- If docs or public agent guidance changes, check whether `npm run generate-agent-context` should be run.
- If API docs or narrative docs are affected, check whether `npm run generate-api-docs`, `npm run generate-narrative-docs`, or `npm run generate-docs` should be run.
- Do not regenerate files blindly. Run the smallest relevant generator for the change.

## Commits and Review

- Do not make mid-task commits. Group related finished work into a logical commit.
- Do not add co-author metadata unless explicitly requested.
- Before proposing a commit or claiming the task is done, review the diff and verify the relevant commands were actually run.
- If you use delegated review or sub-agents, verify their output yourself before repeating their conclusions.

## Verification

- Do not claim work is complete without fresh verification evidence from relevant commands.
- Verify the smallest relevant surface first, then broaden as needed.
- Prefer repo-native commands. Typical examples in this repo include `npx nx test <project>`, `npx nx lint <project>`, `npx nx build <project>`, and doc generation commands when context files change.
- If a task affects only one project, verify that project first instead of defaulting to the whole workspace.
- Report what you actually verified, and call out anything you could not run.

## Codex Notes

- In Codex, prefer `workdir` over `cd`.
- Use `apply_patch` for targeted edits.
- Read the repo state before editing, especially in a dirty worktree.
- Do not revert user changes you did not make unless explicitly asked.
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<p align="center">
<img
src="https://cacheplane.ai/assets/hero.svg"
alt="Angular Agent Framework — The Enterprise Streaming Resource for LangChain and Angular"
alt="Angular Stream Resource — The Enterprise Streaming Resource for LangChain and Angular"
width="100%"
/>
</p>
Expand All @@ -11,13 +11,13 @@
</p>

<p align="center">
<a href="https://www.npmjs.com/package/@cacheplane/angular">
<img alt="npm version" src="https://img.shields.io/npm/v/@cacheplane%2Fangular?color=6C8EFF&labelColor=080B14&style=flat-square" />
<a href="https://www.npmjs.com/package/@cacheplane/stream-resource">
<img alt="npm version" src="https://img.shields.io/npm/v/@cacheplane%2Fstream-resource?color=6C8EFF&labelColor=080B14&style=flat-square" />
</a>
<a href="./LICENSE">
<img alt="License: PolyForm Noncommercial + Commercial" src="https://img.shields.io/badge/license-PolyForm%20Noncommercial%20%2B%20Commercial-6C8EFF?labelColor=080B14&style=flat-square" />
</a>
<a href="https://cacheplane.ai">
<a href="https://angular.dev">
<img alt="Angular 20+" src="https://img.shields.io/badge/Angular-20%2B-6C8EFF?labelColor=080B14&style=flat-square" />
</a>
<a href="https://langchain-ai.github.io/langgraph/">
Expand All @@ -27,14 +27,14 @@

---

`agent()` is the Angular equivalent of LangGraph's React `useStream()` hook — a full-parity implementation built on Angular Signals and the Angular Resource API. It gives enterprise Angular teams the same production-grade streaming primitives available to React developers on LangChain, without compromises or workarounds. Drop it into any Angular 20+ component, point it at your LangGraph Platform endpoint, and get reactive, signal-driven access to streaming state, messages, tool calls, interrupts, and thread history.
`streamResource()` is the Angular equivalent of LangGraph's React `useStream()` hook — a full-parity implementation built on Angular Signals and the Angular Resource API. It gives enterprise Angular teams the same production-grade streaming primitives available to React developers on LangChain, without compromises or workarounds. Drop it into any Angular 20+ component, point it at your LangGraph Platform endpoint, and get reactive, signal-driven access to streaming state, messages, tool calls, interrupts, and thread history.

---

## Install

```bash
npm install @cacheplane/angular
npm install @cacheplane/stream-resource
```

**Peer dependencies:** `@angular/core ^20.0.0 || ^21.0.0`, `@langchain/core ^1.1.0`, `@langchain/langgraph-sdk ^1.7.0`, `rxjs ~7.8.0`
Expand All @@ -45,7 +45,7 @@ npm install @cacheplane/angular

```typescript
import { Component } from '@angular/core';
import { agent } from '@cacheplane/angular';
import { streamResource } from '@cacheplane/stream-resource';
import type { BaseMessage } from '@langchain/core/messages';

@Component({
Expand All @@ -65,7 +65,7 @@ import type { BaseMessage } from '@langchain/core/messages';
`,
})
export class ChatComponent {
chat = agent<{ messages: BaseMessage[] }>({
chat = streamResource<{ messages: BaseMessage[] }>({
apiUrl: 'https://your-langgraph-platform.com',
assistantId: 'my-agent',
messagesKey: 'messages',
Expand All @@ -83,7 +83,7 @@ That's it. `chat.messages()` is an Angular Signal. Bind it directly in your temp

## Feature Comparison

| Feature | `agent()` (Angular) | `useStream()` (React) |
| Feature | `streamResource()` (Angular) | `useStream()` (React) |
|---|---|---|
| Streaming state as reactive primitives | Angular Signals | React state |
| Messages signal | `messages()` | `messages` |
Expand All @@ -99,7 +99,7 @@ That's it. `chat.messages()` is an Angular Signal. Bind it directly in your temp
| Submit | `submit(values, opts?)` | `submit(values, opts?)` |
| Stop | `stop()` | `stop()` |
| Reload last submission | `reload()` | — |
| Custom transport (for testing) | `MockAgentTransport` | mock fetch |
| Custom transport (for testing) | `MockStreamTransport` | mock fetch |
| Angular `ResourceRef<T>` compatibility | Full duck-type parity | N/A |
| Angular 20+ Signals API | Native | N/A |
| SSR / Server Components | Client-side only | React Server Components (React) |
Expand All @@ -111,12 +111,12 @@ That's it. `chat.messages()` is an Angular Signal. Bind it directly in your temp
<p align="center">
<img
src="https://cacheplane.ai/assets/arch-diagram.svg"
alt="Angular Agent Framework architecture: Angular Component → agent() → StreamManager Bridge → LangGraph Platform, with signals returned reactively"
alt="Angular Stream Resource architecture: Angular Component → streamResource() → StreamManager Bridge → LangGraph Platform, with signals returned reactively"
width="100%"
/>
</p>

`agent()` creates 12 `BehaviorSubject`s at injection-context time — once, at component construction. The `StreamManager` bridge (the only file that touches `@langchain/langgraph-sdk` internals) pushes stream events into those subjects. `toSignal()` converts each subject to an Angular Signal, also at construction time. Dynamic actions (`submit`, `stop`, `switchThread`) push into the existing subjects — no new subjects are ever created after construction. This architecture is required because `toSignal()` must be called in an injection context and cannot be called again later.
`streamResource()` creates 12 `BehaviorSubject`s at injection-context time — once, at component construction. The `StreamManager` bridge (the only file that touches `@langchain/langgraph-sdk` internals) pushes stream events into those subjects. `toSignal()` converts each subject to an Angular Signal, also at construction time. Dynamic actions (`submit`, `stop`, `switchThread`) push into the existing subjects — no new subjects are ever created after construction. This architecture is required because `toSignal()` must be called in an injection context and cannot be called again later.

---

Expand All @@ -137,17 +137,17 @@ That's it. `chat.messages()` is an Angular Signal. Bind it directly in your temp

- [Getting Started](https://cacheplane.ai/docs/getting-started)
- [API Reference](https://cacheplane.ai/api-reference)
- [Testing with MockAgentTransport](https://cacheplane.ai/docs/testing)
- [Testing with MockStreamTransport](https://cacheplane.ai/docs/testing)
- [Human-in-the-Loop / Interrupts](https://cacheplane.ai/docs/interrupts)
- [Subagent Streaming](https://cacheplane.ai/docs/subagents)

---

## License

`@cacheplane/angular` is source-available software dual-licensed:
`@cacheplane/stream-resource` is source-available software dual-licensed:

- **PolyForm Noncommercial 1.0.0** — free for noncommercial use (personal projects, academic, research, non-profit internal tooling). See [`LICENSE`](./LICENSE).
- **Angular Agent Framework Commercial License** — required for any for-profit or revenue-generating use. See [`LICENSE-COMMERCIAL`](./LICENSE-COMMERCIAL) and [`COMMERCIAL.md`](./COMMERCIAL.md).
- **Angular Stream Resource Commercial License** — required for any for-profit or revenue-generating use. See [`LICENSE-COMMERCIAL`](./LICENSE-COMMERCIAL) and [`COMMERCIAL.md`](./COMMERCIAL.md).

This is **not** an open-source license. Commercial use — including use in a for-profit product, service, or organization — requires a paid commercial license. See [pricing](https://cacheplane.ai/pricing).
2 changes: 1 addition & 1 deletion apps/cockpit/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./../../dist/apps/cockpit/.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
39 changes: 0 additions & 39 deletions apps/website/content/docs/chat/api/chat-config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,15 @@ import type { ChatConfig } from '@cacheplane/chat';

```typescript
interface ChatConfig {
/** Default render registry for generative UI components. */
renderRegistry?: AngularRegistry;

/** Override the default AI avatar label (default: "A"). */
avatarLabel?: string;

/** Override the default assistant display name (default: "Assistant"). */
assistantName?: string;
}
```

## Properties

### renderRegistry

```typescript
renderRegistry?: AngularRegistry
```

The component registry used by `ChatGenerativeUiComponent` to resolve JSON UI specs to Angular components. This is an `AngularRegistry` from `@cacheplane/render`.

When not provided, generative UI components will not render -- the `chat-generative-ui` primitive will simply show nothing for any spec passed to it.

**Example:**

```typescript
import { createAngularRegistry } from '@cacheplane/render';
import { WeatherCardComponent } from './weather-card.component';
import { ChartComponent } from './chart.component';

const registry = createAngularRegistry({
weather_card: WeatherCardComponent,
chart: ChartComponent,
});

provideChat({ renderRegistry: registry });
```

See the [Generative UI guide](/docs/chat/guides/generative-ui) for detailed setup.

### avatarLabel

```typescript
Expand Down Expand Up @@ -110,14 +79,6 @@ export class ChatHeaderComponent {
}
```

## Relationship to Other Types

`ChatConfig` references the following external type:

| Type | Package | Description |
|------|---------|-------------|
| `AngularRegistry` | `@cacheplane/render` | Maps JSON spec type strings to Angular component classes |

## Type Location

The `ChatConfig` interface is defined in two files within the library:
Expand Down
83 changes: 83 additions & 0 deletions apps/website/content/docs/chat/api/content-classifier.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# createContentClassifier()

Factory function that creates a `ContentClassifier` — a stateful, per-message service that detects content type from the token stream and routes to the appropriate parser.

**Import:**

```typescript
import { createContentClassifier } from '@cacheplane/chat';
import type { ContentClassifier, ContentType } from '@cacheplane/chat';
```

## Signature

```typescript
function createContentClassifier(): ContentClassifier
```

**Returns:** `ContentClassifier` — a stateful classifier instance. Must be called within an Angular injection context (signals require it).

## ContentClassifier Interface

```typescript
interface ContentClassifier {
/** Feed the full message content snapshot. Internally computes delta. */
update(content: string): void;

/** Detected content type. */
readonly type: Signal<ContentType>;

/** Accumulated markdown prose. Empty for pure JSON messages. */
readonly markdown: Signal<string>;

/** Materialized JSON-render spec. Null until JSON is detected. */
readonly spec: Signal<Spec | null>;

/** Per-element accumulation tracking. */
readonly elementStates: Signal<Map<string, ElementAccumulationState>>;

/** True while content is still arriving. */
readonly streaming: Signal<boolean>;

/** Clean up resources. */
dispose(): void;
}
```

## ContentType

```typescript
type ContentType = 'undetermined' | 'markdown' | 'json-render' | 'a2ui' | 'mixed';
```

| Value | Meaning |
|-------|---------|
| `undetermined` | No content received yet |
| `markdown` | Plain text / markdown prose |
| `json-render` | JSON spec detected (first non-whitespace is `{`) |
| `a2ui` | A2UI payload detected (future) |
| `mixed` | Markdown followed by structured content (future) |

## Usage

```typescript
const classifier = createContentClassifier();

// Feed full content snapshots — delta is computed internally
classifier.update('Hello world');
classifier.type(); // 'markdown'
classifier.markdown(); // 'Hello world'

// For JSON content
const jsonClassifier = createContentClassifier();
jsonClassifier.update('{"root":"r1","elements":{}}');
jsonClassifier.type(); // 'json-render'
jsonClassifier.spec(); // { root: 'r1', elements: {} }

// Always dispose when done
classifier.dispose();
```

<Callout type="tip" title="Delta processing">
Pass the **full** message content each time, not just new characters. The classifier tracks `processedLength` internally and only processes the delta.
</Callout>
Loading
Loading