Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 0 additions & 18 deletions apps/website/content/docs/agent/api/api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -733,30 +733,12 @@
"kind": "interface",
"description": "Global configuration for agent instances.\nProperties set here serve as defaults that can be overridden per-call.",
"properties": [
{
"name": "__licenseEnvHint",
"type": "object",
"description": "Test-only env hint override. Not part of the stable API.",
"optional": true
},
{
"name": "__licensePublicKey",
"type": "Uint8Array<ArrayBufferLike>",
"description": "Test-only public-key override. Defaults to the compile-time embedded\n`LICENSE_PUBLIC_KEY`. Not part of the stable API.",
"optional": true
},
{
"name": "apiUrl",
"type": "string",
"description": "Base URL of the LangGraph Platform API (e.g., `'http://localhost:2024'`).",
"optional": true
},
{
"name": "license",
"type": "string",
"description": "Signed license token from threadplane.ai. Optional; omitted in dev.",
"optional": true
},
{
"name": "transport",
"type": "AgentTransport",
Expand Down
4 changes: 1 addition & 3 deletions apps/website/content/docs/agent/api/provide-agent.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# provideAgent()

`provideAgent()` registers global defaults for every `agent()` call in an Angular application. Call it once in `bootstrapApplication` or an `ApplicationConfig` when multiple agents share the same LangGraph API URL, transport, or license token.
`provideAgent()` registers global defaults for every `agent()` call in an Angular application. Call it once in `bootstrapApplication` or an `ApplicationConfig` when multiple agents share the same LangGraph API URL or transport.

Per-call options still win over provider defaults, so you can configure the common case globally and override individual agents when needed.

Expand All @@ -16,7 +16,6 @@ bootstrapApplication(AppComponent, {
providers: [
provideAgent({
apiUrl: 'http://localhost:2024',
license: environment.ngafLicense,
transport: environment.testMode
? new MockAgentTransport()
: undefined,
Expand All @@ -31,7 +30,6 @@ bootstrapApplication(AppComponent, {
|--------|------|-------------|
| `apiUrl` | `string` | Default LangGraph Platform API base URL. Individual `agent()` calls may override it. |
| `transport` | `AgentTransport` | Optional transport instance. Defaults to `FetchStreamTransport` when omitted. |
| `license` | `string` | Optional signed license token. Development and noncommercial use can omit it. |

## Defaults and overrides

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ npm install @ngaf/langgraph @ngaf/chat
| Package | Version |
|---------|---------|
| `@ngaf/chat` | `*` |
| `@ngaf/licensing` | `*` |
| `@angular/core` | `^20.0.0 \|\| ^21.0.0` |
| `@langchain/core` | `^1.1.33` |
| `@langchain/langgraph-sdk` | `^1.7.4` |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Introduction

`@ngaf/licensing` is the shared license-check helper used by the framework packages. It verifies compact Ed25519-signed tokens offline, evaluates the result into a small status set, and emits non-blocking warnings when appropriate.
`@ngaf/licensing` is the shared license-check helper used by `@ngaf/chat` and by custom integrations that opt into the same warning behavior. It verifies compact Ed25519-signed tokens offline, evaluates the result into a small status set, and emits non-blocking warnings when appropriate.

The package itself is MIT licensed. `COMMERCIAL.md` states that the libraries in this repository are free to use, modify, and distribute in commercial and noncommercial projects. The proprietary part called out there is the internal minting service, not this package.

Expand Down Expand Up @@ -67,4 +67,4 @@ The higher-level check is designed not to block app startup:
- warning output goes through `console.warn` unless a custom `warn` function is supplied;
- no network request is made by the licensing check.

The code returns statuses instead of throwing for normal license states. Consumers can choose what to do with the status, but the framework packages use it as a warning and visibility mechanism, not as an app kill switch.
The code returns statuses instead of throwing for normal license states. Consumers can choose what to do with the status, and `@ngaf/chat` uses it as a warning and visibility mechanism, not as an app kill switch.
7 changes: 2 additions & 5 deletions apps/website/content/docs/licensing/guides/ci-and-offline.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@ For CI builds, treat the license token like any other secret:
NGAF_LICENSE=... npm test
```

Then pass it through the framework provider that owns the package you use.
Then pass it through `provideChat()`.

```ts
provideAgent({
apiUrl: process.env['LANGGRAPH_API_URL'],
license: process.env['NGAF_LICENSE'],
});
provideChat({ license: process.env['NGAF_LICENSE'] });
```

If you call `runLicenseCheck()` directly, inject the current time only when you need deterministic tests:
Expand Down
11 changes: 4 additions & 7 deletions apps/website/content/docs/licensing/guides/setup.mdx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
# Setup

Most applications do not import `@ngaf/licensing` directly. Framework packages call `runLicenseCheck()` from their providers when you pass a license token.
Most applications do not import `@ngaf/licensing` directly. `@ngaf/chat` calls `runLicenseCheck()` from `provideChat()` when you pass a license token.

For example, higher-level packages expose a `license` option:
For example, chat exposes a `license` option:

```ts
provideAgent({
apiUrl: 'https://api.example.com',
license: environment.ngafLicense,
});
provideChat({ license: environment.ngafLicense });
```

When no token is supplied, the licensing helper can evaluate the environment as `noncommercial` when the package passes `isNoncommercial: true`.
When no token is supplied, the licensing helper can evaluate the environment as `noncommercial` when the caller passes `isNoncommercial: true`.

## Direct use

Expand Down
18 changes: 0 additions & 18 deletions apps/website/content/docs/render/api/api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,18 +239,6 @@
"kind": "interface",
"description": "",
"properties": [
{
"name": "__licenseEnvHint",
"type": "object",
"description": "Test-only env hint override. Not part of the stable API.",
"optional": true
},
{
"name": "__licensePublicKey",
"type": "Uint8Array<ArrayBufferLike>",
"description": "Test-only public-key override. Defaults to the compile-time embedded\n`LICENSE_PUBLIC_KEY`. Not part of the stable API.",
"optional": true
},
{
"name": "functions",
"type": "Record<string, ComputedFunction>",
Expand All @@ -263,12 +251,6 @@
"description": "",
"optional": true
},
{
"name": "license",
"type": "string",
"description": "Signed license token from threadplane.ai. Optional; omitted in dev.",
"optional": true
},
{
"name": "registry",
"type": "AngularRegistry",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ The library requires the following peer dependencies:
| `@angular/core` | `^20.0.0` or `^21.0.0` |
| `@angular/common` | `^20.0.0` or `^21.0.0` |
| `@json-render/core` | `^0.16.0` |
| `@ngaf/licensing` | `*` |

<Callout type="info" title="Angular packages">
`@angular/core` and `@angular/common` are already part of any Angular 20+ project. You only need to install `@json-render/core` as an additional dependency if your package manager does not install peer dependencies automatically.
Expand Down
14 changes: 12 additions & 2 deletions apps/website/e2e/website.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,19 @@ test('pricing page shows plan cards', async ({ page }) => {
});

test('pricing page lead form validates required fields', async ({ page }) => {
let checkoutCalled = false;
await page.route('**/api/checkout/session', async (route) => {
checkoutCalled = true;
await route.abort();
});

await page.goto('/pricing');
await page.click('button[type="submit"]');
await expect(page.locator('form').first()).toBeVisible();
const leadForm = page.locator('#lead-form form');
await expect(leadForm).toBeVisible();
await leadForm.getByRole('button', { name: 'Get in touch' }).click();
await expect(leadForm).toBeVisible();
expect(checkoutCalled).toBe(false);
expect(await leadForm.evaluate((form) => (form as HTMLFormElement).checkValidity())).toBe(false);
});

test('contact page submits a lead payload and renders success state', async ({ page }) => {
Expand Down
2 changes: 0 additions & 2 deletions libs/ag-ui/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ export default [
'vite',
'@nx/vite',
'vitest',
// peerDeps used by later tasks (stub-only in Task 1)
'@ngaf/licensing',
'fast-json-patch',
'rxjs',
],
Expand Down
1 change: 0 additions & 1 deletion libs/ag-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"version": "0.0.46",
"peerDependencies": {
"@ngaf/chat": "*",
"@ngaf/licensing": "*",
"@angular/core": "^20.0.0 || ^21.0.0",
"@ag-ui/client": "*",
"rxjs": "~7.8.0"
Expand Down
1 change: 0 additions & 1 deletion libs/langgraph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"version": "0.0.46",
"peerDependencies": {
"@ngaf/chat": "*",
"@ngaf/licensing": "*",
"@angular/core": "^20.0.0 || ^21.0.0",
"@langchain/core": "^1.1.33",
"@langchain/langgraph-sdk": "^1.7.4",
Expand Down
58 changes: 11 additions & 47 deletions libs/langgraph/src/lib/agent.provider.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
// SPDX-License-Identifier: MIT
import { beforeEach, describe, it, expect, vi } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { provideAgent, AGENT_CONFIG } from './agent.provider';
import { provideAgent, AGENT_CONFIG, type AgentConfig } from './agent.provider';
import { MockAgentTransport } from './transport/mock-stream.transport';
import {
signLicense,
generateKeyPair,
__resetRunLicenseCheckStateForTests,
__resetNagStateForTests,
} from '@ngaf/licensing/testing';

describe('provideAgent', () => {
beforeEach(() => {
__resetRunLicenseCheckStateForTests();
__resetNagStateForTests();
globalThis.console.warn = vi.fn();
});

it('provides AGENT_CONFIG token', () => {
Expand All @@ -33,48 +26,19 @@ describe('provideAgent', () => {
expect(config.transport).toBe(transport);
});

it('runs a silent license check when a valid license is supplied', async () => {
const warn = vi.fn();
globalThis.console.warn = warn;
const kp = await generateKeyPair();
const token = await signLicense(
{
sub: 'cus_test',
tier: 'developer-seat',
iat: 1_700_000_000,
exp: 2_000_000_000,
seats: 1,
},
kp.privateKey,
);
TestBed.configureTestingModule({
providers: [
provideAgent({
apiUrl: '',
license: token,
// @internal hook — verifies against the ephemeral pair so the test
// doesn't need to know/mint the production public key.
__licensePublicKey: kp.publicKey,
}),
],
});
TestBed.inject(AGENT_CONFIG);
// Allow microtasks from the ed25519 verify + telemetry fire-and-forget.
await new Promise((r) => setTimeout(r, 0));
expect(warn).not.toHaveBeenCalled();
});
it('does not perform license checks because @ngaf/langgraph is MIT-licensed', async () => {
const warn = globalThis.console.warn as ReturnType<typeof vi.fn>;
const legacyLicenseConfig = {
apiUrl: '',
license: 'invalid-token',
__licenseEnvHint: { isNoncommercial: false },
} as unknown as AgentConfig;

it('warns when license is missing and env is production-like', async () => {
const warn = vi.fn();
globalThis.console.warn = warn;
TestBed.configureTestingModule({
providers: [
provideAgent({ apiUrl: '', __licenseEnvHint: { isNoncommercial: false } }),
],
providers: [provideAgent(legacyLicenseConfig)],
});
TestBed.inject(AGENT_CONFIG);
await new Promise((r) => setTimeout(r, 0));
const calls = warn.mock.calls.map((c) => String(c[0]));
expect(calls.some((m) => m.includes('[threadplane] @ngaf/langgraph'))).toBe(true);
expect(warn).not.toHaveBeenCalled();
});
});
29 changes: 0 additions & 29 deletions libs/langgraph/src/lib/agent.provider.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
// SPDX-License-Identifier: MIT
import { InjectionToken, Provider } from '@angular/core';
import {
runLicenseCheck,
LICENSE_PUBLIC_KEY,
inferNoncommercial,
} from '@ngaf/licensing';
import { AgentTransport } from './agent.types';

const PACKAGE_NAME = '@ngaf/langgraph';

/**
* Global configuration for agent instances.
* Properties set here serve as defaults that can be overridden per-call.
Expand All @@ -18,19 +11,6 @@ export interface AgentConfig {
apiUrl?: string;
/** Custom transport implementation. Defaults to {@link FetchStreamTransport}. */
transport?: AgentTransport;
/** Signed license token from threadplane.ai. Optional; omitted in dev. */
license?: string;
/**
* @internal
* Test-only env hint override. Not part of the stable API.
*/
__licenseEnvHint?: { isNoncommercial: boolean };
/**
* @internal
* Test-only public-key override. Defaults to the compile-time embedded
* `LICENSE_PUBLIC_KEY`. Not part of the stable API.
*/
__licensePublicKey?: Uint8Array;
}

export const AGENT_CONFIG = new InjectionToken<AgentConfig>('AGENT_CONFIG');
Expand All @@ -40,14 +20,5 @@ export const AGENT_CONFIG = new InjectionToken<AgentConfig>('AGENT_CONFIG');
* agent instances in the application.
*/
export function provideAgent(config: AgentConfig): Provider {
// Fire-and-forget license check. Never blocks DI resolution.
void runLicenseCheck({
package: PACKAGE_NAME,
token: config.license,
publicKey: config.__licensePublicKey ?? LICENSE_PUBLIC_KEY,
isNoncommercial:
config.__licenseEnvHint?.isNoncommercial ?? inferNoncommercial(),
});

return { provide: AGENT_CONFIG, useValue: config };
}
3 changes: 1 addition & 2 deletions libs/render/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
"peerDependencies": {
"@angular/core": "^20.0.0 || ^21.0.0",
"@angular/common": "^20.0.0 || ^21.0.0",
"@json-render/core": "^0.16.0",
"@ngaf/licensing": "*"
"@json-render/core": "^0.16.0"
},
"license": "MIT",
"repository": {
Expand Down
6 changes: 0 additions & 6 deletions libs/render/src/lib/lifecycle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,11 @@ import { TestBed } from '@angular/core/testing';
import { RenderLifecycleService } from './render-lifecycle.service';
import { RENDER_LIFECYCLE } from './lifecycle';
import { provideRender } from './provide-render';
import {
__resetRunLicenseCheckStateForTests,
__resetNagStateForTests,
} from '@ngaf/licensing/testing';

describe('RenderLifecycle', () => {
let service: RenderLifecycleService;

beforeEach(() => {
__resetRunLicenseCheckStateForTests();
__resetNagStateForTests();
globalThis.console.warn = vi.fn();
TestBed.configureTestingModule({
providers: [provideRender({})],
Expand Down
Loading
Loading