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 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.data()` exposes the underlying `Y.Map` for apps that key their state by screen paths. `ai.rows(prefix)`, `ai.scan(prefix)` and `ai.wipe(prefix)` query and clear repeated entries, `ai.tx(fn, origin?)` groups writes into one transaction, and `ai.history(options?)` is the undo manager over the same map.
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(doc, keys?)` encodes the whole doc or only the named maps. 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 and out of snapshots 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 an open popup.
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 { Server as PartyServer } from 'ai-ax/server'
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 a local doc from a published JSON snapshot without waking a Durable Object at all.
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: the server injects them with `ai.use({ ... })`, and every implementation receives `ai` as its second argument. `.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. Overrides intercept tools too, which is how a browser rewrites the variables the Claude API sends before an action runs.
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`, `snapshot` | Durable Object |
123
- | `ai-ax/client` | `client`, `hydrate`, `createStream`, `ALIAS` | browser |
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
- | keys | `string[]` | whole doc | map names included in the snapshot |
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 { a as StreamItem, c as StreamUpdate, i as hydrate, l as createStream, n as Route, o as StreamSend, r as client, s as StreamTool, t as Link } from "./client-BN8W_6y5.js";
2
- export { Link, Route, type StreamItem, type StreamSend, type StreamTool, type StreamUpdate, client, createStream, hydrate };
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{init as e}from"./index.js";import{ALIAS as t,LLM_PATH as n}from"./const.js";import*as r from"yjs";import i from"y-partyserver/provider";const a=/^\[tool\] (?:(\S+) )?(\S+)$/,o=(e=n)=>{let t=e=>{let t=[{text:``,tools:[]}],n=!1;for(let r of e.split(`
2
- `)){if(r.startsWith(`[usage]`))continue;let e=a.exec(r),i=t[t.length-1];if(e){i.tools.push({server:e[1]??``,name:e[2]}),n=!0;continue}if(!(n&&r.trim()===``)){if(n){t.push({text:r,tools:[]}),n=!1;continue}i.text=i.text===``?r:`${i.text}\n${r}`}}return t.map(e=>({...e,text:e.text.trim()})).filter(e=>e.text!==``||e.tools.length>0)};return{parse:t,send:async(n,r)=>{let{headers:i={},...a}=n,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:n}=await s.read();if(e)break;let i=c.decode(n,{stream:!0});l+=i,r?.({items:t(l),delta:i,raw:l})}return t(l)}}};let s=null,c=new Set;const l=(t={})=>{s?.leave();let{room:n=``,path:a=``,host:o=location.host,party:l=`v1`,doc:u}=t,d=n?new i(o,n,u,{party:l,params:{path:a}}):null,f=d?.doc??u??new r.Doc;return s={doc:f,call:e(f),synced(e){if(!d||d.synced)return void e();d.once(`synced`,()=>e())},leave(){c.forEach(e=>e()),c=new Set,d?.destroy()}}},u=new WeakSet,d=(e,n={})=>{if(u.has(e))return e;u.add(e);let r=l({room:t,...n});return e.use({link:r}),e},f=(e,t,n=(e,t)=>t)=>{if(u.has(e))return e;u.add(e);let r=l({});return r.doc.transact(()=>Object.entries(t).forEach(([e,t])=>r.doc.getMap(`tree`).set(e,n(e,t)))),e.use({link:r}),e};export{d as client,o as createStream,f as hydrate};
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
@@ -1,4 +1,4 @@
1
- import { n as Ai, r as Config } from "./types-CQLXtCsz.js";
1
+ import { Ai, Config } from "./types.js";
2
2
  //#region src/hono.d.ts
3
3
  declare const hono: (ai: Ai<any>, extra?: Partial<Config>) => import("hono").MiddlewareHandler<any, string, {}, Response>;
4
4
  //#endregion
package/dist/hono.js CHANGED
@@ -1,2 +1,2 @@
1
- import{actions as e}from"./index.js";import{PATH_HEADER as t,SUB_HEADER as n}from"./const.js";import{createMiddleware as r}from"hono/factory";import i from"@auth/core/providers/google";import{decode as a,encode as o}from"@auth/core/jwt";import{getAuthUser as s,initAuthConfig as c}from"@hono/auth-js";import{env as l}from"hono/adapter";import{streamText as u}from"hono/streaming";import{StreamableHTTPTransport as d}from"@hono/mcp";import{McpServer as f}from"@modelcontextprotocol/sdk/server/mcp.js";import{getServerByName as p,routePartykitRequest as m}from"partyserver";const h=`authjs.mcp-token`,g=e=>c(t=>{let n=new URL(t.req.url).host,r=!!e.cookie&&e.cookie.services.some(t=>n===`${t}.${e.cookie.apex}`),a={sessionToken:{options:{domain:`.${e.cookie?.apex}`}}},o=e.providers??[i({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`},...r?{cookies:a}:{}}}),_=async e=>(await s(e))?.token?.sub??``,v=(e,t,n,r)=>o({token:{sub:t,path:n},secret:l(e).AUTH_SECRET,salt:r?.salt??h,maxAge:r?.maxAge??43200}),y=async(e,t)=>{let n=(e.req.header(`Authorization`)??``).replace(/^Bearer\s+/i,``);if(!n)return null;let r=await a({token:n,secret:l(e).AUTH_SECRET,salt:t?.salt??h}).catch(()=>null);return{sub:r?.sub??``,path:r?.path??``}},b=e=>e.startsWith(`data:`)?JSON.parse(e.slice(5)):null,x=(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})}),S=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=b(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}},C=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 S(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 x(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`)},w=e=>r(async t=>{let n=await _(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:l(t),sub:n,path:r}))return t.text(`Forbidden`,403);let c=e.host??t.req.header(`x-original-host`)??t.req.header(`x-forwarded-host`)??new URL(t.req.url).host,d={key:l(t).ANTHROPIC_API_KEY,api:e.claude??`https://api.anthropic.com/v1/messages`,name:e.name??`aiax`,url:`https://${c}${e.endpoint??`/api/mcp`}`,model:a||e.model||`claude-sonnet-5`,system:e.system??``,maxTokens:e.maxTokens??32e3},f=()=>s?Promise.resolve(``):v(t,n,r,e),p=[...o,{role:`user`,content:i}],m=await x(d,await f(),p);return m.ok?u(t,t=>C(d,f,p,m,e=>t.write(e),e.maxTurns??10)):t.text(await m.text(),500)}),T=async(e,r,i,a,o)=>{let s=await p(e,r),c={[n]:i.sub,[t]:encodeURIComponent(i.path)};return(await s.fetch(`https://internal/act`,{method:`POST`,headers:c,body:JSON.stringify({name:a,input:o})})).json()},E=t=>r(async n=>{let r=await y(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:l(n),sub:i,path:a}),!o)return n.text(`Forbidden`,403)}let s=new f({name:t.name??`aiax`,version:t.version??`1.0.0`}),c=new d;return e.forEach((e,r)=>s.registerTool(r,{description:e.description,inputSchema:{...e.input}},async e=>{let s=await T(l(n)[t.binding??`v1`],o,{sub:i,path:a},r,e);return{content:[{type:`text`,text:JSON.stringify(s)}]}})),await s.connect(c),c.handleRequest(n)}),D=async(e,r,i,a)=>{let o=new Headers(e.req.raw.headers);o.set(n,i),o.set(t,encodeURIComponent(a));let s=new URL(e.req.url),c=s.pathname.split(`/`);c[3]=encodeURIComponent(r),s.pathname=c.join(`/`);let u=new Request(s,new Request(e.req.raw,{headers:o}));return await m(u,l(e))??e.text(`Not Found`,404)},O=e=>r(async t=>{let n=decodeURIComponent(t.req.path.split(`/`)[3]??``),r=t.req.query(`path`)??``,i=await _(t);if(e.lobby&&n===e.lobby)return D(t,n,i,r);if(!i)return t.text(`Unauthorized`,401);let a=await(e.own??(()=>``))({env:l(t),sub:i,path:r});return!a||n!==a&&n!==(e.alias??`my-room`)?t.text(`Forbidden`,403):D(t,a,i,r)}),k=(e,t={})=>{let n=Object.assign(e.config,t),i={auth:g(n),party:O(n),mcp:E(n),llm:w(n)};return r(async(e,t)=>{let r=()=>e.req.path.startsWith(`/parties/`)?i.party(e,t):e.req.path===(n.endpoint??`/api/mcp`)?i.mcp(e,t):e.req.path===(n.relay??`/api/llm`)&&e.req.method===`POST`?i.llm(e,t):t(),a;if(await i.auth(e,async()=>void(a=await r())),a)return a})};export{k as hono};
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 { a as Define, c as Peer, i as Cookie, l as Saved, n as Ai, o as Grant, r as Config, s as Own, t as ActionDef, u as Side } from "./types-CQLXtCsz.js";
2
- import { a as current, c as isBound, i as call, l as side, n as actions, o as data, r as bind, s as init, t as Call, u as x } from "./index-DNUoYl4_.js";
3
- export { type ActionDef, type Ai, Call, type Config, type Cookie, type Define, type Grant, type Own, type Peer, type Saved, type Side, actions, bind, call, current, data, init, isBound, side, x };
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=new Map,n=typeof window>`u`?`server`:`client`;let r,i;const a=e=>i=e,o=()=>!!i,s=(e=`tree`)=>i.getMap(e),c=(e,t)=>r(e,t),l=(e,i=e=>e())=>{a(e);let o=e.getMap(`act-req`),s=e.getMap(`act-res`),c=new Map,l=async r=>{let c=o.get(r),l=t.get(c?.name);!l||l.side!==n||(o.delete(r),s.set(r,{value:await i(()=>(a(e),l.run(c.input)))}))},u=e=>{let t=c.get(e);!t||!s.has(e)||(c.delete(e),t(s.get(e).value),s.delete(e))};return o.observe(e=>e.keysChanged.forEach(l)),s.observe(e=>e.keysChanged.forEach(u)),r=(r,i)=>{let s=t.get(r);return s.side===n||s.side===`both`?(a(e),s.run(i)):new Promise(e=>{let t=crypto.randomUUID();c.set(t,e),o.set(t,{name:r,input:i})})},r},u={ai:null},d=()=>u.ai,f=new WeakMap,p=t=>{let n=s();return f.has(n)||f.set(n,new e.UndoManager(n,t)),f.get(n)},m=e=>!!e&&typeof e.parse==`function`,h=e=>m(e)?{value:e}:e,g=(e,t)=>m(e)&&t&&typeof t==`object`&&`value`in t?t.value:t;function _(e={},r={}){let i={},a=new Map,l=new Set,d=[],f=(e,t)=>n=>{let r=a.get(e);return r?r(n,t,y):t(n)},m=e=>(n,...r)=>{let i=r.find(e=>typeof e==`string`)??``,a=r.find(e=>typeof e==`function`),o=r.find(e=>e&&typeof e==`object`),s=async e=>{let t=g(o,e),n=await a(t,y);return typeof n==`function`?n(t,y):n&&n.default?n.default(t,y):n},c=[...d,i].filter(Boolean).join(` `);return t.set(n,{name:n,description:c,side:e,input:h(o),run:f(n,s)}),y},_=e=>()=>Promise.reject(Error(`not implemented: ${e}`)),v={config:r,context:i,side:n,action:m(`both`),client:m(`client`),server:m(`server`),describe(e,t){return t?(d=[...d,e],t(y),d=d.slice(0,-1),y):y},abstract:e=>(l.add(e),y),override:(e,t)=>(a.set(e,t),y),use:e=>(Object.assign(i,e),y),data:e=>s(e),rows(e){let t=[];return s().forEach((n,r)=>r.startsWith(e)&&!r.slice(e.length).includes(`/`)&&t.push(n)),t.sort((e,t)=>(e?.pos??0)-(t?.pos??0))},scan(e){let t=[];return s().forEach((n,r)=>r.startsWith(e)&&t.push(n)),t},wipe(e){let t=[];s().forEach((n,r)=>r.startsWith(e)&&t.push(r)),t.forEach(e=>s().delete(e))},tx:(e,t)=>s().doc.transact(e,t),history:e=>p(e),call:(e,t)=>c(e,t)};t.set(`get_tree`,{name:`get_tree`,description:r.tree??`共有 tree の全 key/value を dump して画面の現在状態を観測する。操作前の観測用`,side:`both`,run:f(`get_tree`,async()=>{let e={};return s().forEach((t,n)=>e[n]=t),e})});let y=new Proxy(v,{get(n,r){if(typeof r!=`string`||r in n)return n[r];if(t.has(r))return e=>c(r,e);if(l.has(r))return f(r,_(r));if(r in i)return i[r];if(r!==`then`&&r!==`toJSON`)return o()?s().get(r)??e[r]:e[r]},set(t,n,r){return typeof n!=`string`||n in t?(t[n]=r,!0):n in i?(i[n]=r,!0):o()?(s().set(n,r),!0):(e[n]=r,!0)}});return u.ai=y,y}export{t as actions,a as bind,c as call,d as current,s as data,l as init,o as isBound,n as side,_ as x};
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
@@ -1,4 +1,4 @@
1
- import { n as Route } from "./client-BN8W_6y5.js";
1
+ import { Route } from "./types.js";
2
2
  import { ReactNode } from "react";
3
3
  //#region src/react.d.ts
4
4
  declare const atom: <T>(initial: T) => {
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),a=e.link;return t(()=>{let t=()=>i(e=>e+1);if(!a)return void import(`./client.js`).then(({client:n})=>(n(e,r),t()));let n=a.doc.getMap(`tree`);return n.observe(t),()=>n.unobserve(t)},[a]),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};
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 { o as Grant, r as Config } from "./types-CQLXtCsz.js";
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: any;
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: 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, snapshot };
65
+ export { Server };
package/dist/server.js CHANGED
@@ -1 +1 @@
1
- import{actions as e,bind as t,current as n,init as r}from"./index.js";import"./const.js";import*as i from"yjs";import{YServer as a}from"y-partyserver";const o=(e,t)=>{if(!t)return i.encodeStateAsUpdate(e);let n=new i.Doc;return n.transact(()=>{t.forEach(t=>{let r=e.getMap(t),a=n.getMap(t);r.forEach((e,t)=>a.set(t,e instanceof i.AbstractType?e.clone():e))})}),i.encodeStateAsUpdate(n)};var s=class extends a{static options={hibernate:!0};static callbackOptions={debounceWait:6e4,debounceMaxWait:6e4};ai=n();sub=``;path=``;grant=null;queue=Promise.resolve();hook=e=>e();wrap=e=>{let t=this.grant;this.grant=null;let n=async()=>(this.ai.use({env:this.env,room:this.name,...t??await this.owner()}),this.hook(e));return this.queue=this.queue.then(n,n),this.queue};call=r(this.document,e=>this.wrap(e));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(){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(){let e=this.conf().load;if(!e||this.isLobby())return;let t=await e(this.saved(await this.owner()));t&&t.byteLength&&i.applyUpdate(this.document,new Uint8Array(t),`load`)}async onSave(){let e=this.conf().save;if(!e||this.isLobby())return;let t=await this.owner();t.sub&&await e(this.saved(t),o(this.document,this.conf().keys))}async onAlarm(){await this.onSave()}async onClose(e,t,n,r){await super.onClose(e,t,n,r),await this.onSave()}async onRequest(n){if(t(this.document),n.method!==`POST`)return new Response(`Not Found`,{status:404});let{name:r,input:i}=await n.json();return e.get(r)?.side===`client`?Response.json({ok:!0,result:await this.call(r,i)}):(await this.onAct(this.grantOf(n),n),Response.json({ok:!0,result:await this.wrap(()=>this.call(r,i))}))}};export{s as Server,o as snapshot};
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 { a as Define, c as Peer, i as Cookie, l as Saved, n as Ai, o as Grant, r as Config, s as Own, t as ActionDef, u as Side } from "./types-CQLXtCsz.js";
2
- export { ActionDef, Ai, Config, Cookie, Define, Grant, Own, Peer, Saved, Side };
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.5.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": [