ai-ax 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -8
- package/dist/client.d.ts +33 -2
- package/dist/client.js +2 -2
- package/dist/hono.d.ts +1 -1
- package/dist/hono.js +2 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.js +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/server.d.ts +25 -6
- package/dist/server.js +1 -1
- package/dist/types.d.ts +99 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,9 +51,9 @@ The declared names and schemas flow into the type of `ai`: `ai.overwrite({ count
|
|
|
51
51
|
|
|
52
52
|
### One tree
|
|
53
53
|
|
|
54
|
-
All shared state lives in a single yjs map. State declared in the first argument is read and written through plain properties — `ai.count = 3` above syncs to every connected browser and to the agent's next `get_tree` — and `ai.
|
|
54
|
+
All shared state lives in a single yjs map. State declared in the first argument is read and written through plain properties — `ai.count = 3` above syncs to every connected browser and to the agent's next `get_tree` — and any undeclared key travels the same proxy: `ai['files/a.txt'] = entry`, `ai['files/a.txt']` and `delete ai['files/a.txt']` are how repeated rows live under a `name/id` convention. `ai.rows(prefix)` lists the direct children of a prefix sorted by `pos`, `ai.scan(prefix)` lists every descendant, `ai.wipe(prefix)` clears them, `ai.tx(fn, origin?)` groups writes into one transaction, and `ai.history(options?)` is the undo manager over the same map. `ai.data()` exposes the underlying `Y.Map` only for what a property can never express — `observe` subscriptions and whole-tree iteration.
|
|
55
55
|
|
|
56
|
-
The tree is also the persistence unit. `config.load` restores a snapshot when the doc first wakes, `config.save` persists one on the update debounce, on a durable alarm and on every disconnect — which is what survives Durable Object hibernation — and `snapshot(
|
|
56
|
+
The tree is also the persistence unit. `config.load` restores a snapshot when the doc first wakes, `config.save` persists one on the update debounce, on a durable alarm and on every disconnect — which is what survives Durable Object hibernation — and `ai.snapshot()` encodes it. `config.ui` names the volatile keys (a trailing `/` marks a prefix): they still sync and still show up in `get_tree`, but they are excluded from both the snapshot and the undo capture, so a saved file holds the work and not the open menus. `config.history` merges extra `Y.UndoManager` options such as `captureTimeout`. A `lobby` room skips persistence entirely, so an anonymous top page stays playable without touching storage. State that should stay out of the shared tree entirely deliberately steps off the road: the `ai-ax/react` entry ships a small atom store (`atom`, `setAtom`, `useAtomValue`) for browser-local values like an IME buffer or a chat stream.
|
|
57
57
|
|
|
58
58
|
```ts
|
|
59
59
|
x(
|
|
@@ -77,11 +77,14 @@ The app touches the library through the one `ai` object, and each runtime has ex
|
|
|
77
77
|
// index.ts (worker + Durable Object)
|
|
78
78
|
import { Hono } from 'hono'
|
|
79
79
|
import { hono } from 'ai-ax/hono'
|
|
80
|
+
import { Server } from 'ai-ax/server'
|
|
80
81
|
import { PROMPT } from './prompt'
|
|
81
82
|
import ai from './actions'
|
|
82
83
|
|
|
83
84
|
export default new Hono().use('*', hono(ai, { system: PROMPT }))
|
|
84
|
-
export
|
|
85
|
+
export class PartyServer extends Server {
|
|
86
|
+
ai = ai
|
|
87
|
+
}
|
|
85
88
|
```
|
|
86
89
|
|
|
87
90
|
```tsx
|
|
@@ -95,9 +98,9 @@ const App = () => {
|
|
|
95
98
|
}
|
|
96
99
|
```
|
|
97
100
|
|
|
98
|
-
`hono(ai, extra?)` routes `/parties/*` to websocket sync, the MCP endpoint, the LLM relay and the Auth.js session, and lets everything else fall through to the app's own routes; `extra` merges server-only config such as the system prompt or an Auth.js `adapter`, so they never enter the browser bundle. `Server` is the Durable Object: it records the owning user across hibernation, serializes action execution, and drives the `load` / `save` hooks. `useClient(ai)` connects once and re-renders on every tree change (`Client` is the component form, `client(ai)` the non-React form), and `hydrate(ai, entries)` fills
|
|
101
|
+
`hono(ai, extra?)` routes `/parties/*` to websocket sync, the MCP endpoint, the LLM relay and the Auth.js session, and lets everything else fall through to the app's own routes; `extra` merges server-only config such as the system prompt or an Auth.js `adapter`, so they never enter the browser bundle. `Server` is the Durable Object: the app subclass assigns it the one `ai` (`class PartyServer extends Server { ai = ai }`), and it records the owning user across hibernation, serializes action execution, and drives the `load` / `save` hooks. `useClient(ai)` connects once and re-renders on every tree change (`Client` is the component form, `client(ai)` the non-React form; `route.synced` fires after the first sync, and `route.room: ''` opens the local doc without any connection), and `hydrate(ai, entries)` fills that local doc from a published JSON snapshot without waking a Durable Object at all.
|
|
99
102
|
|
|
100
|
-
Runtime-only values — Cloudflare bindings, the authorized user, the room — travel the same road instead of module globals
|
|
103
|
+
Runtime-only values — Cloudflare bindings, the authorized user, the room — travel the same road instead of module globals. `.abstract(name)` declares a function that actions may call before anyone has implemented it, and `.override(name, (input, next, ai) => ...)` supplies or wraps implementations onion-style, where `next(input)` runs whatever was underneath. The Durable Object overrides one well-known abstract before every execution: `ai.peer()` returns the authorized `{ env, room, sub, path }`, and everything else an app needs (an R2 client, a sandbox runner) is an abstract the app declares in its actions and overrides in its `PartyServer`. Overrides intercept tools too, which is how a browser rewrites the variables the Claude API sends before an action runs.
|
|
101
104
|
|
|
102
105
|
`createStream()` from `ai-ax/client` is the browser side of the relay's wire format: `POST` to the relay streams plain text in which a `[tool]` line marks each MCP call and a trailing `[usage]` line closes the turn, and `parse` / `send` / prior-`messages` replay are the pieces an app needs to render and continue conversations without touching the format itself.
|
|
103
106
|
|
|
@@ -119,8 +122,8 @@ Browsers always connect to the alias room `my-room`, and the worker rewrites it
|
|
|
119
122
|
| -------------- | -------------------------------------------------------- | ----------------------- |
|
|
120
123
|
| `ai-ax` | `x` | everywhere (isomorphic) |
|
|
121
124
|
| `ai-ax/hono` | `hono` | worker |
|
|
122
|
-
| `ai-ax/server` | `Server
|
|
123
|
-
| `ai-ax/client` | `client`, `hydrate`, `createStream
|
|
125
|
+
| `ai-ax/server` | `Server` | Durable Object |
|
|
126
|
+
| `ai-ax/client` | `client`, `hydrate`, `createStream` | browser |
|
|
124
127
|
| `ai-ax/react` | `useClient`, `Client`, `atom`, `setAtom`, `useAtomValue` | browser (React) |
|
|
125
128
|
| `ai-ax/const` | protocol constants (paths, header names) | both |
|
|
126
129
|
|
|
@@ -135,7 +138,8 @@ The worker expects a Durable Object binding named `v1` pointing at the exported
|
|
|
135
138
|
| lobby | `string` | none | one anonymous room without persistence |
|
|
136
139
|
| load | `(at: Saved) => buf` | none | snapshot restore on first doc load |
|
|
137
140
|
| save | `(at: Saved, buf) => void` | none | snapshot persist on debounce / alarm / disconnect |
|
|
138
|
-
|
|
|
141
|
+
| ui | `string[]` | `[]` | volatile keys (trailing `/` = prefix), no undo / save |
|
|
142
|
+
| history | `object` | none | extra `Y.UndoManager` options (e.g. `captureTimeout`) |
|
|
139
143
|
| alias | `string` | `'my-room'` | connection name meaning "my room", rewritten by `own` |
|
|
140
144
|
| system | `string` | `''` | system prompt for the LLM |
|
|
141
145
|
| model | `string` | `'claude-sonnet-5'` | Claude API model id |
|
package/dist/client.d.ts
CHANGED
|
@@ -1,2 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { Route } from "./types.js";
|
|
2
|
+
//#region src/client/stream.d.ts
|
|
3
|
+
interface StreamTool {
|
|
4
|
+
server: string;
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
interface StreamItem {
|
|
8
|
+
text: string;
|
|
9
|
+
tools: StreamTool[];
|
|
10
|
+
error?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface StreamSend {
|
|
13
|
+
prompt: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
messages?: any[];
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
interface StreamUpdate {
|
|
20
|
+
items: StreamItem[];
|
|
21
|
+
delta: string;
|
|
22
|
+
raw: string;
|
|
23
|
+
}
|
|
24
|
+
declare const createStream: (endpoint?: string) => {
|
|
25
|
+
parse: (raw: string) => StreamItem[];
|
|
26
|
+
send: (input: StreamSend, update?: (u: StreamUpdate) => void) => Promise<StreamItem[]>;
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/client.d.ts
|
|
30
|
+
declare const client: <T extends object>(ai: T, route?: Route) => T;
|
|
31
|
+
declare const hydrate: <T extends object>(ai: T, entries: Record<string, any>, wake?: (key: string, value: any) => any) => T;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { type Route, type StreamItem, type StreamSend, type StreamTool, type StreamUpdate, client, createStream, hydrate };
|
package/dist/client.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`)){if(
|
|
1
|
+
import{ALIAS as e,LLM_PATH as t}from"./const.js";import n from"y-partyserver/provider";const r=/^\[tool\] (?:(\S+) )?(\S+)$/,i=(e=t)=>{let n=e=>{let t=[{text:``,tools:[]}],n=!1;for(let i of e.split(`
|
|
2
|
+
`)){if(i.startsWith(`[usage]`))continue;let e=r.exec(i),a=t[t.length-1];if(e){a.tools.push({server:e[1]??``,name:e[2]}),n=!0;continue}if(!(n&&i.trim()===``)){if(n){t.push({text:i,tools:[]}),n=!1;continue}a.text=a.text===``?i:`${a.text}\n${i}`}}return t.map(e=>({...e,text:e.text.trim()})).filter(e=>e.text!==``||e.tools.length>0)};return{parse:n,send:async(t,r)=>{let{headers:i={},...a}=t,o=await fetch(e,{method:`POST`,headers:{"content-type":`application/json`,...i},body:JSON.stringify(a)});if(!o.ok){let e=(await o.text()).replace(/\n/g,` `),t=[{text:e,tools:[],error:!0}];return r?.({items:t,delta:e,raw:e}),t}let s=o.body.getReader(),c=new TextDecoder,l=``;for(;;){let{done:e,value:t}=await s.read();if(e)break;let i=c.decode(t,{stream:!0});l+=i,r?.({items:n(l),delta:i,raw:l})}return n(l)}}},a=new WeakSet,o=(t,r={})=>{if(a.has(t))return t;a.add(t);let{room:i=e,path:o=``,host:s=location.host,party:c=`v1`,synced:l}=r,u=t.open().data().doc,d=i?new n(s,i,u,{party:c,params:{path:o}}):null;return l&&(!d||d.synced?l():d.once(`synced`,()=>l())),t},s=(e,t,n=(e,t)=>t)=>{if(a.has(e))return e;a.add(e);let r=e;return r.open().tx(()=>Object.entries(t).forEach(([e,t])=>r[e]=n(e,t))),e};export{o as client,i as createStream,s as hydrate};
|
package/dist/hono.d.ts
CHANGED
package/dist/hono.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`);u=f.pop()??``;for(let e of f){let c=
|
|
1
|
+
import{PATH_HEADER as e,SUB_HEADER as t}from"./const.js";import{createMiddleware as n}from"hono/factory";import r from"@auth/core/providers/google";import{decode as i,encode as a}from"@auth/core/jwt";import{getAuthUser as o,initAuthConfig as s}from"@hono/auth-js";import{env as c}from"hono/adapter";import{streamText as l}from"hono/streaming";import{StreamableHTTPTransport as u}from"@hono/mcp";import{McpServer as d}from"@modelcontextprotocol/sdk/server/mcp.js";import{getServerByName as f,routePartykitRequest as p}from"partyserver";const m=`authjs.mcp-token`,h=e=>s(t=>{let n=new URL(t.req.url).host,i=!!e.cookie&&e.cookie.services.some(t=>n===`${t}.${e.cookie.apex}`),a={sessionToken:{options:{domain:`.${e.cookie?.apex}`}}},o=e.providers??[r({clientId:t.env.GOOGLE_CLIENT_ID,clientSecret:t.env.GOOGLE_CLIENT_SECRET})];return{...e.adapter?{adapter:e.adapter(t.env)}:{},providers:o,secret:t.env.AUTH_SECRET,session:{strategy:`jwt`},...i?{cookies:a}:{}}}),g=async e=>(await o(e))?.token?.sub??``,_=(e,t,n,r)=>a({token:{sub:t,path:n},secret:c(e).AUTH_SECRET,salt:r?.salt??m,maxAge:r?.maxAge??43200}),v=async(e,t)=>{let n=(e.req.header(`Authorization`)??``).replace(/^Bearer\s+/i,``);if(!n)return null;let r=await i({token:n,secret:c(e).AUTH_SECRET,salt:t?.salt??m}).catch(()=>null);return{sub:r?.sub??``,path:r?.path??``}},y=e=>e.startsWith(`data:`)?JSON.parse(e.slice(5)):null,b=(e,t,n)=>fetch(e.api,{method:`POST`,headers:{"x-api-key":e.key,"anthropic-version":`2023-06-01`,"anthropic-beta":`mcp-client-2025-11-20`,"content-type":`application/json`},body:JSON.stringify({model:e.model,max_tokens:e.maxTokens,stream:!0,cache_control:{type:`ephemeral`},system:e.system,mcp_servers:[{type:`url`,url:e.url,name:e.name,...t?{authorization_token:t}:{}}],tools:[{type:`mcp_toolset`,mcp_server_name:e.name}],messages:n})}),x=async(e,t)=>{let n=[],r=null,i=0,a=0,o=0,s=``,c=e.body.getReader(),l=new TextDecoder,u=``;for(;;){let{done:e,value:d}=await c.read();if(e)break;u+=l.decode(d,{stream:!0});let f=u.split(`
|
|
2
|
+
`);u=f.pop()??``;for(let e of f){let c=y(e);if(c){if(c.type===`message_start`){let e=c.message?.usage??{};i+=(e.input_tokens??0)+(e.cache_creation_input_tokens??0)+(e.cache_read_input_tokens??0),a+=e.cache_read_input_tokens??0}c.type===`message_delta`&&(o=c.usage?.output_tokens??o,s=c.delta?.stop_reason??s),c.type===`content_block_start`&&(r={...c.content_block},n.push(r),r.type===`mcp_tool_use`&&await t(`\n[tool] ${r.server_name} ${r.name}\n`)),c.type===`content_block_delta`&&c.delta?.type===`text_delta`&&(r.text=(r.text??``)+c.delta.text,await t(c.delta.text)),c.type===`content_block_delta`&&c.delta?.type===`input_json_delta`&&(r.partial=(r.partial??``)+c.delta.partial_json),c.type===`content_block_delta`&&c.delta?.type===`thinking_delta`&&(r.thinking=(r.thinking??``)+c.delta.thinking),c.type===`content_block_delta`&&c.delta?.type===`signature_delta`&&(r.signature=(r.signature??``)+c.delta.signature),c.type===`content_block_stop`&&r?.partial!==void 0&&(r.input=r.partial?JSON.parse(r.partial):{},delete r.partial)}}}return{blocks:n,input:i,cached:a,output:o,stop:s}},S=async(e,t,n,r,i,a=10)=>{let o=r,s=0,c=0,l=0,u=``;for(let r=0;r<a;r++){let r=await x(o,i);if(s+=r.input,c+=r.cached,l+=r.output,u=r.stop||u,u!==`pause_turn`)break;if(n.push({role:`assistant`,content:r.blocks.filter(e=>e.type!==`thinking`||e.thinking||e.signature)}),o=await b(e,await t(),n),!o.ok){await i(`\n[error] ${(await o.text()).replace(/\n/g,` `)}\n`);break}}await i(`\n[usage] input ${s} (cache ${c}) / output ${l} tokens (${u})\n`)},C=e=>n(async t=>{let n=await g(t),{path:r=``,prompt:i,model:a=``,messages:o=[]}=await t.req.json(),s=!!e.lobby&&!r;if(!s&&!n)return t.text(`Unauthorized`,401);if(!s&&!await(e.own??(()=>``))({env:c(t),sub:n,path:r}))return t.text(`Forbidden`,403);let u=e.host??t.req.header(`x-original-host`)??t.req.header(`x-forwarded-host`)??new URL(t.req.url).host,d={key:c(t).ANTHROPIC_API_KEY,api:e.claude??`https://api.anthropic.com/v1/messages`,name:e.name??`aiax`,url:`https://${u}${e.endpoint??`/api/mcp`}`,model:a||e.model||`claude-sonnet-5`,system:e.system??``,maxTokens:e.maxTokens??32e3},f=()=>s?Promise.resolve(``):_(t,n,r,e),p=[...o,{role:`user`,content:i}],m=await b(d,await f(),p);return m.ok?l(t,t=>S(d,f,p,m,e=>t.write(e),e.maxTurns??10)):t.text(await m.text(),500)}),w=async(n,r,i,a,o)=>{let s=await f(n,r),c={[t]:i.sub,[e]:encodeURIComponent(i.path)};return(await s.fetch(`https://internal/act`,{method:`POST`,headers:c,body:JSON.stringify({name:a,input:o})})).json()},T=(e,t)=>n(async n=>{let r=await v(n,t),i=r?.sub??``,a=r?.path??``,o=t.lobby??``;if(r||!o){if(!i)return n.text(`Unauthorized`,401);if(o=await(t.own??(()=>``))({env:c(n),sub:i,path:a}),!o)return n.text(`Forbidden`,403)}let s=new d({name:t.name??`aiax`,version:t.version??`1.0.0`}),l=new u;return e.actions.forEach((e,r)=>s.registerTool(r,{description:e.description,inputSchema:{...e.input}},async e=>{let s=await w(c(n)[t.binding??`v1`],o,{sub:i,path:a},r,e);return{content:[{type:`text`,text:JSON.stringify(s)}]}})),await s.connect(l),l.handleRequest(n)}),E=async(n,r,i,a)=>{let o=new Headers(n.req.raw.headers);o.set(t,i),o.set(e,encodeURIComponent(a));let s=new URL(n.req.url),l=s.pathname.split(`/`);l[3]=encodeURIComponent(r),s.pathname=l.join(`/`);let u=new Request(s,new Request(n.req.raw,{headers:o}));return await p(u,c(n))??n.text(`Not Found`,404)},D=e=>n(async t=>{let n=decodeURIComponent(t.req.path.split(`/`)[3]??``),r=t.req.query(`path`)??``,i=await g(t);if(e.lobby&&n===e.lobby)return E(t,n,i,r);if(!i)return t.text(`Unauthorized`,401);let a=await(e.own??(()=>``))({env:c(t),sub:i,path:r});return!a||n!==a&&n!==(e.alias??`my-room`)?t.text(`Forbidden`,403):E(t,a,i,r)}),O=(e,t={})=>{let r=Object.assign(e.config,t),i={auth:h(r),party:D(r),mcp:T(e,r),llm:C(r)};return n(async(e,t)=>{let n=()=>e.req.path.startsWith(`/parties/`)?i.party(e,t):e.req.path===(r.endpoint??`/api/mcp`)?i.mcp(e,t):e.req.path===(r.relay??`/api/llm`)&&e.req.method===`POST`?i.llm(e,t):t(),a;if(await i.auth(e,async()=>void(a=await n())),a)return a})};export{O as hono};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { ActionDef, Ai, Config, Cookie, Define, Grant, Own, Peer, Saved, Side } from "./types.js";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
declare const side: Side;
|
|
4
|
+
declare function x<P extends object>(params: P, config?: Config): Ai<P>;
|
|
5
|
+
declare function x<P extends object = {}>(params?: Partial<P>, config?: Config): Ai<P>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { type ActionDef, type Ai, type Config, type Cookie, type Define, type Grant, type Own, type Peer, type Saved, type Side, side, x };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as e from"yjs";const t=
|
|
1
|
+
import*as e from"yjs";const t=(t,n)=>{let r=t.ui??[],i=e=>r.some(t=>t.endsWith(`/`)?e.startsWith(t):e===t),a=new WeakMap,o=e=>{for(let t of e.changed.values())for(let e of t)if(e===null||!i(e))return!0;return!1};return{rows(e){let t=[];return n().forEach((n,r)=>r.startsWith(e)&&!r.slice(e.length).includes(`/`)&&t.push(n)),t.sort((e,t)=>(e?.pos??0)-(t?.pos??0))},history(r){let i=n();return a.has(i)||a.set(i,new e.UndoManager(i,{captureTransaction:o,...t.history,...r})),a.get(i)},snapshot(){let t=new e.Doc,r=t.getMap(`tree`);return t.transact(()=>n().forEach((t,n)=>!i(n)&&r.set(n,t instanceof e.AbstractType?t.clone():t))),e.encodeStateAsUpdate(t)}}},n=(t,n)=>{let r=null,i=()=>r??=new e.Doc,a=new WeakSet,o=new WeakMap,s=new Map,c=(e,l)=>{e&&(r=e),l&&o.set(i(),l);let u=i();if(a.has(u))return;a.add(u);let d=u.getMap(`act-req`),f=u.getMap(`act-res`),p=async e=>{let r=d.get(e),i=n.get(r?.name);if(!i||i.side!==t)return;d.delete(e);let a=o.get(u)??(e=>e());f.set(e,{value:await a(()=>(c(u),i.run(r.input)))})},m=e=>{let t=s.get(e);!t||!f.has(e)||(s.delete(e),t(f.get(e).value),f.delete(e))};d.observe(e=>e.keysChanged.forEach(p)),f.observe(e=>e.keysChanged.forEach(m))};return{open:c,data:(e=`tree`)=>i().getMap(e),call(e,r){let a=n.get(e);return a?a.side===t||a.side===`both`?(c(),Promise.resolve(a.run(r))):new Promise(t=>{let n=crypto.randomUUID();s.set(n,t),i().getMap(`act-req`).set(n,{name:e,input:r})}):Promise.reject(Error(`unknown action: ${e}`))}}},r=e=>!!e&&typeof e.parse==`function`,i=e=>r(e)?{value:e}:e,a=(e,t)=>r(e)&&t&&typeof t==`object`&&`value`in t?t.value:t,o=(e,t,n)=>{let r=[],o=(e,r)=>i=>{let a=t.get(e);return a?a(i,r,n()):r(i)};return{chain:o,missing:e=>()=>Promise.reject(Error(`not implemented: ${e}`)),describe(e,t){return t?(r=[...r,e],t(n()),r=r.slice(0,-1),n()):n()},define:t=>(s,...c)=>{let l=c.find(e=>typeof e==`string`)??``,u=c.find(e=>typeof e==`function`),d=c.find(e=>e&&typeof e==`object`),f=async e=>{let t=a(d,e),r=await u(t,n());return typeof r==`function`?r(t,n()):r&&r.default?r.default(t,n()):r},p=[...r,l].filter(Boolean).join(` `);return e.set(s,{name:s,description:p,side:t,input:i(d),run:o(s,f)}),n()}}},s=typeof window>`u`?`server`:`client`;function c(e={},r={}){let i=new Map,a=new Map,c=new Set,l=n(s,i),u=t(r,l.data),d=o(i,a,()=>m),f=async()=>{let e={};return l.data().forEach((t,n)=>e[n]=t),e},p={config:r,side:s,actions:i,action:d.define(`both`),client:d.define(`client`),server:d.define(`server`),describe:d.describe,abstract:e=>(c.add(e),m),override:(e,t)=>(a.set(e,t),m),data:l.data,open:(e,t)=>(l.open(e,t),m),call:l.call,rows:u.rows,scan:e=>{let t=[];return l.data().forEach((n,r)=>r.startsWith(e)&&t.push(n)),t},wipe:e=>{let t=[];l.data().forEach((n,r)=>r.startsWith(e)&&t.push(r)),t.forEach(e=>l.data().delete(e))},tx:(e,t)=>l.data().doc.transact(e,t),history:u.history,snapshot:u.snapshot};i.set(`get_tree`,{name:`get_tree`,description:r.tree??`Dump every key/value of the shared tree to observe the current state before acting`,side:`both`,run:d.chain(`get_tree`,f)});let m=new Proxy(p,{get(t,n){if(typeof n!=`string`||n in t)return t[n];if(i.has(n))return e=>l.call(n,e);if(c.has(n))return d.chain(n,d.missing(n));if(n!==`then`&&n!==`toJSON`)return l.data().get(n)??e[n]},set(e,t,n){return typeof t!=`string`||t in e?(e[t]=n,!0):(l.data().set(t,n),!0)},deleteProperty(e,t){return typeof t!=`string`||t in e||l.data().delete(t),!0}});return m}export{s as side,c as x};
|
package/dist/react.d.ts
CHANGED
package/dist/react.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback as e,useEffect as t,useState as n}from"react";const r=e=>({value:e,listeners:new Set}),i=(e,t)=>{typeof t==`function`&&(t=t(e.value)),e.value=t,e.listeners.forEach(e=>e(t))},a=e=>{let[r,i]=n(()=>e.value);return t(()=>{let t=e=>i(e);return e.listeners.add(t),r!==e.value&&i(e.value),()=>void e.listeners.delete(t)},[e]),r},o=t=>[a(t),e(e=>i(t,e),[t])],s=({atom:e,children:t})=>t(a(e)),c=(e,r)=>{let[,i]=n(0)
|
|
1
|
+
import{useCallback as e,useEffect as t,useState as n}from"react";const r=e=>({value:e,listeners:new Set}),i=(e,t)=>{typeof t==`function`&&(t=t(e.value)),e.value=t,e.listeners.forEach(e=>e(t))},a=e=>{let[r,i]=n(()=>e.value);return t(()=>{let t=e=>i(e);return e.listeners.add(t),r!==e.value&&i(e.value),()=>void e.listeners.delete(t)},[e]),r},o=t=>[a(t),e(e=>i(t,e),[t])],s=({atom:e,children:t})=>t(a(e)),c=(e,r)=>{let[,i]=n(0);return t(()=>{import(`./client.js`).then(({client:t})=>t(e,r));let t=e.data(),n=()=>i(e=>e+1);return t.observe(n),()=>t.unobserve(n)},[e]),e},l=({ai:e,route:t,children:n})=>n(c(e,t));export{s as AtomValue,l as Client,r as atom,i as setAtom,o as useAtom,a as useAtomValue,c as useClient};
|
package/dist/server.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Call } from "./index-DNUoYl4_.js";
|
|
1
|
+
import { ActionDef, Ai, Config, Define, Grant, Side } from "./types.js";
|
|
3
2
|
import * as Y from "yjs";
|
|
4
3
|
import { YServer } from "y-partyserver";
|
|
5
4
|
//#region src/server.d.ts
|
|
6
|
-
declare const snapshot: (doc: Y.Doc, keys?: string[]) => Uint8Array;
|
|
7
5
|
declare class Server extends YServer {
|
|
8
6
|
static options: {
|
|
9
7
|
hibernate: boolean;
|
|
@@ -14,14 +12,35 @@ declare class Server extends YServer {
|
|
|
14
12
|
};
|
|
15
13
|
env: any;
|
|
16
14
|
ctx: any;
|
|
17
|
-
ai:
|
|
15
|
+
ai: Ai;
|
|
18
16
|
sub: string;
|
|
19
17
|
path: string;
|
|
20
18
|
grant: Grant | null;
|
|
21
19
|
queue: Promise<any>;
|
|
22
20
|
hook: (run: () => Promise<any>) => Promise<any>;
|
|
21
|
+
wire: () => {
|
|
22
|
+
config: Config;
|
|
23
|
+
side: Side;
|
|
24
|
+
actions: Map<string, ActionDef>;
|
|
25
|
+
action: Define<{}>;
|
|
26
|
+
client: Define<{}>;
|
|
27
|
+
server: Define<{}>;
|
|
28
|
+
describe(text: string): /*elided*/ any;
|
|
29
|
+
describe<U extends object>(text: string, fn: (ai: /*elided*/ any) => Ai<U>): Ai<U>;
|
|
30
|
+
abstract<N extends string>(name: N): Ai<Record<N, (input?: any) => any>>;
|
|
31
|
+
override(name: string, fn: (input: any, next: (input: any) => Promise<any>, ai: any) => any): /*elided*/ any;
|
|
32
|
+
data<V = any>(name?: string): Y.Map<V>;
|
|
33
|
+
open(doc?: Y.Doc, wrap?: (run: () => Promise<any>) => Promise<any>): /*elided*/ any;
|
|
34
|
+
call(name: string, input?: any): Promise<any>;
|
|
35
|
+
rows<V = any>(prefix: string): V[];
|
|
36
|
+
scan<V = any>(prefix: string): V[];
|
|
37
|
+
wipe(prefix: string): void;
|
|
38
|
+
tx(fn: () => void, origin?: any): void;
|
|
39
|
+
history(options?: object): Y.UndoManager;
|
|
40
|
+
snapshot(): Uint8Array;
|
|
41
|
+
};
|
|
23
42
|
wrap: (run: () => Promise<any>) => Promise<any>;
|
|
24
|
-
call:
|
|
43
|
+
call: (name: string, input?: any) => Promise<any>;
|
|
25
44
|
conf(): Config;
|
|
26
45
|
isLobby(): boolean;
|
|
27
46
|
saved(grant: Grant): {
|
|
@@ -43,4 +62,4 @@ declare class Server extends YServer {
|
|
|
43
62
|
onRequest(request: Request): Promise<Response>;
|
|
44
63
|
}
|
|
45
64
|
//#endregion
|
|
46
|
-
export { Server
|
|
65
|
+
export { Server };
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import"./const.js";import*as e from"yjs";import{YServer as t}from"y-partyserver";var n=class extends t{static options={hibernate:!0};static callbackOptions={debounceWait:6e4,debounceMaxWait:6e4};ai=null;sub=``;path=``;grant=null;queue=Promise.resolve();hook=e=>e();wire=()=>this.ai.open(this.document,e=>this.wrap(e));wrap=e=>{let t=this.grant;this.grant=null;let n=async()=>{let n={env:this.env,room:this.name,...t??await this.owner()};return this.wire(),this.ai.override(`peer`,()=>n),this.hook(e)};return this.queue=this.queue.then(n,n),this.queue};call=(e,t)=>(this.wire(),this.ai.call(e,t));conf(){return this.ai?.config??{}}isLobby(){let e=this.conf().lobby;return!!e&&this.name===e}saved(e){return{env:this.env,sub:e.sub,path:e.path,room:this.name}}grantOf(e){return{sub:e.headers.get(`x-user-sub`)??``,path:decodeURIComponent(e.headers.get(`x-user-path`)??``)}}async owner(){for(let e of this.getConnections()){let t=e.state;if(t?.sub)return t}return this.sub?{sub:this.sub,path:this.path}:await this.ctx.storage.get(`owner`)??{sub:``,path:``}}async remember(e,t){this.sub=e||this.sub,this.path=t||this.path,e&&await this.ctx.storage.put(`owner`,{sub:e,path:t})}async onAct(e,t){this.grant=e,await this.remember(e.sub,e.path)}async onStart(){this.wire(),await super.onStart(),this.conf().save&&this.document.on(`update`,()=>{this.isLobby()||this.ctx.storage.setAlarm(Date.now()+7e4)})}async onConnect(e,t){let{sub:n,path:r}=this.grantOf(t.request);return e.setState({sub:n,path:r}),await this.remember(n,r),super.onConnect(e,t)}async onLoad(){this.wire();let t=this.conf().load;if(!t||this.isLobby())return;let n=await t(this.saved(await this.owner()));n&&n.byteLength&&e.applyUpdate(this.document,new Uint8Array(n),`load`)}async onSave(){let e=this.conf().save;if(!e||this.isLobby())return;let t=await this.owner();t.sub&&(this.wire(),await e(this.saved(t),this.ai.snapshot()))}async onAlarm(){await this.onSave()}async onClose(e,t,n,r){await super.onClose(e,t,n,r),await this.onSave()}async onRequest(e){if(this.wire(),e.method!==`POST`)return new Response(`Not Found`,{status:404});let{name:t,input:n}=await e.json();return this.ai.actions.get(t)?.side===`client`?Response.json({ok:!0,result:await this.call(t,n)}):(await this.onAct(this.grantOf(e),e),Response.json({ok:!0,result:await this.wrap(()=>this.call(t,n))}))}};export{n as Server};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,2 +1,99 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import * as Y from "yjs";
|
|
2
|
+
import { ZodRawShape, ZodType } from "zod";
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
type Side = 'server' | 'client' | 'both';
|
|
5
|
+
interface ActionDef {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
side: Side;
|
|
9
|
+
input?: ZodRawShape;
|
|
10
|
+
run: (input: any) => Promise<any>;
|
|
11
|
+
}
|
|
12
|
+
interface Grant {
|
|
13
|
+
sub: string;
|
|
14
|
+
path: string;
|
|
15
|
+
}
|
|
16
|
+
interface Peer {
|
|
17
|
+
env: any;
|
|
18
|
+
sub: string;
|
|
19
|
+
path: string;
|
|
20
|
+
}
|
|
21
|
+
interface Saved extends Peer {
|
|
22
|
+
room: string;
|
|
23
|
+
}
|
|
24
|
+
type Own = (at: Peer) => Promise<string> | string;
|
|
25
|
+
interface Cookie {
|
|
26
|
+
apex: string;
|
|
27
|
+
services: string[];
|
|
28
|
+
}
|
|
29
|
+
interface Route {
|
|
30
|
+
room?: string;
|
|
31
|
+
path?: string;
|
|
32
|
+
host?: string;
|
|
33
|
+
party?: string;
|
|
34
|
+
synced?: () => void;
|
|
35
|
+
}
|
|
36
|
+
interface Config {
|
|
37
|
+
name?: string;
|
|
38
|
+
tree?: string;
|
|
39
|
+
own?: Own;
|
|
40
|
+
adapter?: (env: any) => any;
|
|
41
|
+
lobby?: string;
|
|
42
|
+
alias?: string;
|
|
43
|
+
system?: string;
|
|
44
|
+
model?: string;
|
|
45
|
+
claude?: string;
|
|
46
|
+
maxTokens?: number;
|
|
47
|
+
maxTurns?: number;
|
|
48
|
+
binding?: string;
|
|
49
|
+
host?: string;
|
|
50
|
+
endpoint?: string;
|
|
51
|
+
relay?: string;
|
|
52
|
+
version?: string;
|
|
53
|
+
salt?: string;
|
|
54
|
+
maxAge?: number;
|
|
55
|
+
cookie?: Cookie;
|
|
56
|
+
providers?: any[];
|
|
57
|
+
ui?: string[];
|
|
58
|
+
history?: object;
|
|
59
|
+
load?: (at: Saved) => Promise<any>;
|
|
60
|
+
save?: (at: Saved, buf: Uint8Array) => Promise<any>;
|
|
61
|
+
}
|
|
62
|
+
type AnyFn = (...args: any[]) => any;
|
|
63
|
+
type Shape = ZodRawShape | ZodType;
|
|
64
|
+
type Value<Z> = Z extends ZodType<infer O> ? O : any;
|
|
65
|
+
type Opt<S> = { [K in keyof S]: undefined extends Value<S[K]> ? K : never; }[keyof S];
|
|
66
|
+
type Input<S> = S extends ZodType<infer O> ? O : { [K in Exclude<keyof S, Opt<S>>]: Value<S[K]>; } & { [K in Opt<S>]?: Value<S[K]>; };
|
|
67
|
+
type Result<F> = F extends AnyFn ? (Awaited<ReturnType<F>> extends {
|
|
68
|
+
default: infer D;
|
|
69
|
+
} ? (D extends AnyFn ? Awaited<ReturnType<D>> : Awaited<ReturnType<F>>) : Awaited<ReturnType<F>>) : never;
|
|
70
|
+
type Part = string | Shape | AnyFn;
|
|
71
|
+
type FnOf<A> = A extends readonly [infer H, ...infer R] ? (H extends AnyFn ? H : FnOf<R>) : never;
|
|
72
|
+
type SchemaOf<A> = A extends readonly [infer H, ...infer R] ? (H extends AnyFn ? SchemaOf<R> : H extends string ? SchemaOf<R> : H) : unknown;
|
|
73
|
+
type Tool<S, F> = unknown extends S ? (input?: any) => Promise<Result<F>> : (input: Input<S>) => Promise<Result<F>>;
|
|
74
|
+
interface Define<T extends object> {
|
|
75
|
+
<N extends string, A extends readonly [Part, ...Part[]]>(name: N, ...parts: A): Ai<T & Record<N, Tool<SchemaOf<A>, FnOf<A>>>>;
|
|
76
|
+
}
|
|
77
|
+
type Ai<T extends object = {}> = T & {
|
|
78
|
+
config: Config;
|
|
79
|
+
side: Side;
|
|
80
|
+
actions: Map<string, ActionDef>;
|
|
81
|
+
action: Define<T>;
|
|
82
|
+
client: Define<T>;
|
|
83
|
+
server: Define<T>;
|
|
84
|
+
describe(text: string): Ai<T>;
|
|
85
|
+
describe<U extends object>(text: string, fn: (ai: Ai<T>) => Ai<U>): Ai<T & U>;
|
|
86
|
+
abstract<N extends string>(name: N): Ai<T & Record<N, (input?: any) => any>>;
|
|
87
|
+
override(name: string, fn: (input: any, next: (input: any) => Promise<any>, ai: any) => any): Ai<T>;
|
|
88
|
+
data<V = any>(name?: string): Y.Map<V>;
|
|
89
|
+
open(doc?: Y.Doc, wrap?: (run: () => Promise<any>) => Promise<any>): Ai<T>;
|
|
90
|
+
call(name: string, input?: any): Promise<any>;
|
|
91
|
+
rows<V = any>(prefix: string): V[];
|
|
92
|
+
scan<V = any>(prefix: string): V[];
|
|
93
|
+
wipe(prefix: string): void;
|
|
94
|
+
tx(fn: () => void, origin?: any): void;
|
|
95
|
+
history(options?: object): Y.UndoManager;
|
|
96
|
+
snapshot(): Uint8Array;
|
|
97
|
+
};
|
|
98
|
+
//#endregion
|
|
99
|
+
export { ActionDef, Ai, Config, Cookie, Define, Grant, Own, Peer, Route, Saved, Side };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-ax",
|
|
3
3
|
"author": "tseijp",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "AI Agent Experience (AI/AX) library for Hono + Cloudflare. One act surface for both human UI/UX and LLM MCP tools, with authentication, authorization, realtime sync and Claude API relay.",
|
|
7
7
|
"keywords": [
|