diff --git a/docs/integrations.mdx b/docs/integrations.mdx index 5eb3f18897..40a07fa90b 100644 --- a/docs/integrations.mdx +++ b/docs/integrations.mdx @@ -23,6 +23,7 @@ that demonstrate the protocol's capabilities and versatility. - **[CrewAI](https://crewai.com)** - Streamline workflows across industries with powerful AI agents. - **[AG2](https://ag2.ai)** - The Open-Source AgentOS +- **[OpenClaw](https://github.com/openclaw/openclaw)** - Open-source, self-hostable agent runtime ## Infrastructure / Deployment diff --git a/integrations/openclaw/typescript/.gitignore b/integrations/openclaw/typescript/.gitignore new file mode 100644 index 0000000000..0ccb8df8de --- /dev/null +++ b/integrations/openclaw/typescript/.gitignore @@ -0,0 +1,141 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.* +!.env.example + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist +.output + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Sveltekit cache directory +.svelte-kit/ + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Firebase cache directory +.firebase/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v3 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Vite files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +.vite/ diff --git a/integrations/openclaw/typescript/.npmignore b/integrations/openclaw/typescript/.npmignore new file mode 100644 index 0000000000..4d5a3822de --- /dev/null +++ b/integrations/openclaw/typescript/.npmignore @@ -0,0 +1,12 @@ +.nx +.DS_Store +.git +.gitignore +.idea +.vscode +.env +__tests__ +src +tsdown.config.ts +tsconfig.json +vitest.config.ts diff --git a/integrations/openclaw/typescript/README.md b/integrations/openclaw/typescript/README.md new file mode 100644 index 0000000000..55a12030a0 --- /dev/null +++ b/integrations/openclaw/typescript/README.md @@ -0,0 +1,68 @@ +# OpenClaw + +Implementation of the AG-UI protocol for [OpenClaw](https://github.com/openclaw/openclaw). + +`OpenClawAgent` connects an AG-UI / CopilotKit frontend to an OpenClaw gateway +that speaks the AG-UI protocol over HTTP/SSE through the +[`clawg-ui`](https://www.npmjs.com/package/@contextableai/clawg-ui) channel +plugin. The plugin self-registers an AG-UI endpoint on the gateway (the +operator-auth route `/v1/clawg-ui/operator`), so the client talks to it exactly +like any other self-hosted AG-UI HTTP backend — no framework-specific transport +is required. + +`OpenClawAgent` is a thin extension of the standard +[`HttpAgent`](https://www.npmjs.com/package/@ag-ui/client). OpenClaw emits the +current AG-UI protocol (streaming text, reasoning, tool calls), so no protocol +version capping is applied. + +## Features + +- **Operator-token auth built in** — pass the gateway token as `gatewayToken` and + it is attached as `Authorization: Bearer ` on every request; no manual + header assembly. +- **Standard AG-UI transport** — a thin `HttpAgent` subclass, so it works with any + AG-UI / CopilotKit frontend over HTTP/SSE with no framework-specific glue. +- **No protocol capping** — OpenClaw emits the current AG-UI protocol (streaming + text, reasoning, tool calls), so no version ceiling is applied. + +## Installation + +```shell +npm install @ag-ui/openclaw +# or +pnpm add @ag-ui/openclaw +# or +yarn add @ag-ui/openclaw +``` + +`@ag-ui/core`, `@ag-ui/client`, and `rxjs` are peer dependencies. + +## Usage + +```ts +import { OpenClawAgent } from "@ag-ui/openclaw"; + +const agent = new OpenClawAgent({ + // The clawg-ui operator route exposed by the OpenClaw gateway. + url: "http://localhost:8000/v1/clawg-ui/operator", + // The gateway operator token — sent as `Authorization: Bearer `. + gatewayToken: process.env.OPENCLAW_GATEWAY_TOKEN, + // Seed the conversation; the agent replies to these on the first run. + initialMessages: [{ id: "1", role: "user", content: "Hello from AG-UI!" }], +}); + +const result = await agent.runAgent(); +``` + +`OpenClawAgent` accepts the same configuration as `HttpAgent` (`url`, `headers`, +`fetch`, `agentId`, `threadId`, `initialMessages`, `initialState`, `debug`), plus +`gatewayToken` — a convenience that becomes the `Authorization: Bearer` header +(authoritative over any `Authorization` passed in `headers`). + +## Prerequisites + +An OpenClaw gateway running with the `clawg-ui` plugin installed and a gateway +token configured. See the [OpenClaw](https://github.com/openclaw/openclaw) +documentation for setting up and running a gateway, and the +[`clawg-ui`](https://www.npmjs.com/package/@contextableai/clawg-ui) plugin for +installing and exposing the AG-UI endpoint. diff --git a/integrations/openclaw/typescript/package.json b/integrations/openclaw/typescript/package.json new file mode 100644 index 0000000000..481bf212b8 --- /dev/null +++ b/integrations/openclaw/typescript/package.json @@ -0,0 +1,56 @@ +{ + "name": "@ag-ui/openclaw", + "version": "0.0.1", + "repository": { + "type": "git", + "url": "https://github.com/ag-ui-protocol/ag-ui.git" + }, + "author": "AG-UI Team", + "license": "Apache-2.0", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "sideEffects": false, + "files": [ + "dist/**", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsdown", + "dev": "tsdown --watch", + "clean": "git clean -fdX --exclude=\"!.env\"", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "test:watch": "vitest", + "test:exports": "publint --strict && attw --pack", + "link:global": "pnpm link --global", + "unlink:global": "pnpm unlink --global" + }, + "peerDependencies": { + "@ag-ui/core": ">=0.0.37", + "@ag-ui/client": ">=0.0.37", + "rxjs": "7.8.1" + }, + "devDependencies": { + "@ag-ui/core": "workspace:*", + "@ag-ui/client": "workspace:*", + "@arethetypeswrong/cli": "^0.17.4", + "@types/node": "^20.11.19", + "@vitest/coverage-istanbul": "^4.0.18", + "publint": "^0.3.12", + "tsdown": "^0.20.1", + "typescript": "^5.3.3", + "vitest": "^4.0.18" + }, + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./package.json": "./package.json" + } +} diff --git a/integrations/openclaw/typescript/src/index.test.ts b/integrations/openclaw/typescript/src/index.test.ts new file mode 100644 index 0000000000..b45688093d --- /dev/null +++ b/integrations/openclaw/typescript/src/index.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect } from "vitest"; +import { OpenClawAgent } from "./index"; +import { HttpAgent } from "@ag-ui/client"; + +const URL = "http://localhost:8000/v1/clawg-ui/operator"; + +describe("OpenClawAgent", () => { + it("is a subclass of HttpAgent", () => { + expect(OpenClawAgent.prototype).toBeInstanceOf(HttpAgent); + }); + + it("constructs with just a url", () => { + const agent = new OpenClawAgent({ url: URL }); + expect(agent).toBeInstanceOf(OpenClawAgent); + expect(agent).toBeInstanceOf(HttpAgent); + }); + + it("maps gatewayToken to an Authorization: Bearer header", () => { + const agent = new OpenClawAgent({ url: URL, gatewayToken: "secret-token" }); + expect(agent.headers.Authorization).toBe("Bearer secret-token"); + }); + + it("sends no Authorization header when gatewayToken is omitted", () => { + const agent = new OpenClawAgent({ url: URL }); + expect(agent.headers.Authorization).toBeUndefined(); + }); + + it("gatewayToken wins over Authorization in headers, other headers preserved", () => { + const agent = new OpenClawAgent({ + url: URL, + gatewayToken: "token-wins", + headers: { Authorization: "Bearer old", "X-Custom": "keep" }, + }); + expect(agent.headers.Authorization).toBe("Bearer token-wins"); + expect(agent.headers["X-Custom"]).toBe("keep"); + }); +}); diff --git a/integrations/openclaw/typescript/src/index.ts b/integrations/openclaw/typescript/src/index.ts new file mode 100644 index 0000000000..7c22f2e78f --- /dev/null +++ b/integrations/openclaw/typescript/src/index.ts @@ -0,0 +1,45 @@ +/** + * OpenClaw integration for the AG-UI protocol. + * + * Connects an AG-UI / CopilotKit frontend to an OpenClaw gateway that speaks the + * AG-UI protocol over HTTP/SSE via the `clawg-ui` channel plugin (e.g. the + * operator route `/v1/clawg-ui/operator`). OpenClaw speaks the current AG-UI + * protocol, so the standard `HttpAgent` transport handles the request/response + * as-is — no version capping required. + * + * @see https://github.com/openclaw/openclaw + */ + +import { HttpAgent, type HttpAgentConfig } from "@ag-ui/client"; + +/** + * Config for {@link OpenClawAgent}. Extends the base `HttpAgent` config with a + * first-class `gatewayToken` — the OpenClaw gateway operator token — so callers + * pass the secret directly instead of hand-assembling an `Authorization` header. + */ +export interface OpenClawAgentConfig extends HttpAgentConfig { + /** + * OpenClaw gateway operator token. When set, the agent sends + * `Authorization: Bearer ` on every request to the clawg-ui + * operator route. Omit (or leave empty) to send no auth header. + */ + gatewayToken?: string; +} + +/** + * `HttpAgent` for an OpenClaw gateway's `clawg-ui` operator route. Its one + * addition over `HttpAgent` is the `gatewayToken` convenience: pass the operator + * token and it becomes the `Authorization: Bearer` header (authoritative over any + * `Authorization` supplied in `headers`). + */ +export class OpenClawAgent extends HttpAgent { + constructor({ gatewayToken, headers, ...rest }: OpenClawAgentConfig) { + super({ + ...rest, + headers: { + ...headers, + ...(gatewayToken ? { Authorization: `Bearer ${gatewayToken}` } : {}), + }, + }); + } +} diff --git a/integrations/openclaw/typescript/tsconfig.json b/integrations/openclaw/typescript/tsconfig.json new file mode 100644 index 0000000000..a7e91b190b --- /dev/null +++ b/integrations/openclaw/typescript/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "esnext", + "lib": ["dom", "dom.iterable", "esnext"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "node", + "skipLibCheck": true, + "strict": true, + "jsx": "react-jsx", + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "types": ["vitest/globals"], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "stripInternal": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/integrations/openclaw/typescript/tsdown.config.ts b/integrations/openclaw/typescript/tsdown.config.ts new file mode 100644 index 0000000000..6f3030ec48 --- /dev/null +++ b/integrations/openclaw/typescript/tsdown.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["cjs", "esm"], + dts: true, + exports: true, + fixedExtension: false, + sourcemap: true, + clean: true, + minify: true, +}); diff --git a/integrations/openclaw/typescript/vitest.config.ts b/integrations/openclaw/typescript/vitest.config.ts new file mode 100644 index 0000000000..5d97ce5f01 --- /dev/null +++ b/integrations/openclaw/typescript/vitest.config.ts @@ -0,0 +1,21 @@ +import path from "path"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + include: ["**/*.test.ts"], + passWithNoTests: true, + coverage: { + provider: "istanbul", + reporter: ["text", "json", "html"], + reportsDirectory: "./coverage", + }, + }, + resolve: { + alias: { + "@/": path.resolve(__dirname, "./src") + "/", + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fef454e553..c4334508fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -991,6 +991,40 @@ importers: specifier: ^5.9.3 version: 5.9.3 + integrations/openclaw/typescript: + dependencies: + rxjs: + specifier: 7.8.1 + version: 7.8.1 + devDependencies: + '@ag-ui/client': + specifier: workspace:* + version: link:../../../sdks/typescript/packages/client + '@ag-ui/core': + specifier: workspace:* + version: link:../../../sdks/typescript/packages/core + '@arethetypeswrong/cli': + specifier: ^0.17.4 + version: 0.17.4 + '@types/node': + specifier: ^20.11.19 + version: 20.19.21 + '@vitest/coverage-istanbul': + specifier: ^4.0.18 + version: 4.0.18(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.19.21)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.4)) + publint: + specifier: ^0.3.12 + version: 0.3.17 + tsdown: + specifier: ^0.20.1 + version: 0.20.1(publint@0.3.17)(typescript@5.9.3) + typescript: + specifier: ^5.3.3 + version: 5.9.3 + vitest: + specifier: ^4.0.18 + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@20.19.21)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.4) + integrations/pydantic-ai/typescript: dependencies: rxjs: @@ -2158,10 +2192,6 @@ packages: resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} @@ -2295,11 +2325,6 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.29.2': resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} @@ -2822,10 +2847,6 @@ packages: resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} - engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} @@ -3008,6 +3029,7 @@ packages: '@copilotkitnext/shared@0.0.0-mme-ag-ui-0-0-46-20260227141603': resolution: {integrity: sha512-tbw37m+MgOO58dxYsXvGTN9YqHt6DPLMqtDEQftJHrUrQkNqXOxhOporx4p2DG0R+RiQqWrT+r44D2eRCQhlkA==} engines: {node: '>=18'} + deprecated: Use @copilotkit/shared instead. '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -13861,14 +13883,14 @@ snapshots: '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 + '@babel/generator': 7.29.1 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.2 '@babel/template': 7.27.2 '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -13878,14 +13900,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.5': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - '@babel/generator@7.29.1': dependencies: '@babel/parser': 7.29.2 @@ -14078,10 +14092,6 @@ snapshots: '@babel/template': 7.27.2 '@babel/types': 7.29.0 - '@babel/parser@7.28.5': - dependencies: - '@babel/types': 7.28.5 - '@babel/parser@7.29.2': dependencies: '@babel/types': 7.29.0 @@ -14735,11 +14745,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.28.5': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -20785,7 +20790,7 @@ snapshots: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 eslint: 9.37.0(jiti@2.6.1) - get-tsconfig: 4.12.0 + get-tsconfig: 4.13.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 @@ -20857,7 +20862,7 @@ snapshots: eslint-plugin-react-hooks@7.0.1(eslint@9.37.0(jiti@2.6.1)): dependencies: '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.2 eslint: 9.37.0(jiti@2.6.1) hermes-parser: 0.25.1 zod: 3.25.76 @@ -22428,10 +22433,10 @@ snapshots: jest-snapshot@29.7.0: dependencies: '@babel/core': 7.28.5 - '@babel/generator': 7.28.5 + '@babel/generator': 7.29.1 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3