ai-ax 0.4.3 → 0.5.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 +106 -98
- package/dist/client.d.ts +2 -49
- package/dist/client.js +2 -2
- package/dist/hono.d.ts +5 -0
- package/dist/hono.js +2 -0
- package/dist/index.d.ts +3 -16
- package/dist/index.js +1 -2
- package/dist/react.d.ts +23 -0
- package/dist/react.js +1 -0
- package/dist/server.d.ts +38 -6
- package/dist/server.js +1 -1
- package/dist/types.d.ts +2 -42
- package/package.json +28 -12
- package/README.ja.md +0 -147
- package/dist/action.d.ts +0 -13
- package/dist/action.js +0 -1
package/README.md
CHANGED
|
@@ -2,146 +2,154 @@
|
|
|
2
2
|
|
|
3
3
|
AI Agent Experience (AI/AX) library for Hono + Cloudflare.
|
|
4
4
|
|
|
5
|
-
ai-ax
|
|
5
|
+
ai-ax builds the human experience (UI/UX) and the AI agent experience (AI/AX) of a web app as one surface. An operation is declared exactly once, and that one declaration is a click handler for the person watching the screen and an MCP tool for the LLM at the same time, backed by one realtime state that both of them read and write.
|
|
6
6
|
|
|
7
|
-
Three ideas carry the
|
|
7
|
+
Three ideas carry the library — **one action**, **one tree**, **one road** — and each of them is a mechanism you can point at, not a slogan: one action is a single tool registry that serves the UI, the MCP server and the cross-side transport; one tree is a single yjs map that is rendered as the screen, dumped as the agent's observation and encoded as the persistence snapshot; one road is a single `ai` object that every runtime adapter receives.
|
|
8
8
|
|
|
9
9
|
## Why
|
|
10
10
|
|
|
11
|
-
The
|
|
11
|
+
The AI agent has become one more user of the web site. The web, however, was built on the assumption that a human is the one operating it, so when an agent path is bolted onto an app as a second route beside the browser, everything doubles: two operation surfaces, two auth stacks, two sets of bugs, roughly twice the code to manage. The two copies inevitably drift apart, and the drift is usually discovered weeks later in a log.
|
|
12
12
|
|
|
13
|
-
ai-ax removes the second copy instead of maintaining it. The
|
|
13
|
+
ai-ax removes the second copy instead of maintaining it. The person and the agent read the same state and call the same operations, so the declaration of an operation _is_ its MCP tool, and the implementation lives in exactly one place. Every operation is synchronized in realtime through partyserver, whether it arrived from a browser click or from an MCP request, so the agent's moves appear on the human's screen as they happen.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
**One action** — the smallest unit of operation is declared once and reached from both the UI and MCP. **One tree** — all state lives in a single yjs tree, which humans see as the rendered screen and the LLM observes as `get_tree`. **One resolver** — authorization is a single function that decides which room a user may enter; every path, human or AI, goes through it.
|
|
15
|
+
Authentication is the one place where the two users genuinely differ, because an MCP request from the Claude API cannot carry the browser's session. ai-ax closes that gap with a signed token: when the user sends a prompt, the Hono side mints a token and hands it to the Claude API, the token comes back on every MCP request, and the MCP server verifies the signature before executing anything.
|
|
18
16
|
|
|
19
17
|
## What
|
|
20
18
|
|
|
21
|
-
The assumed stack is Hono
|
|
22
|
-
|
|
23
|
-

|
|
24
|
-
|
|
25
|
-
The worker needs one composite middleware, and the Durable Object is a re-export.
|
|
19
|
+
The assumed stack is Hono on Cloudflare Workers. Realtime sync is partyserver — the successor of partykit, which was deprecated after becoming a Cloudflare product — running yjs docs on Durable Objects deployed straight to Cloudflare. Authentication is Auth.js, and the LLM is the Claude API with its MCP connector. Hono itself is confined to the `ai-ax/hono` entry, so every other entry stays framework free.
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
import { Hono } from 'hono'
|
|
29
|
-
import { createAiax, ownSelf } from 'ai-ax'
|
|
30
|
-
import { PROMPT } from './prompt'
|
|
31
|
-
import './actions'
|
|
21
|
+
The whole library is one loop: the browser and the agent join the same room, and every operation — clicked or tool-called — lands on the same doc and syncs back to both.
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
const app = new Hono().use('*', ai.all())
|
|
35
|
-
|
|
36
|
-
export default app
|
|
37
|
-
export { AiaxServer } from 'ai-ax/server'
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
`ai.all()` routes `/parties/*` to websocket sync, `/api/mcp` to the MCP server, `POST /api/llm` to the Claude relay, and handles Auth.js sessions; everything else falls through to your own routes. The exported `AiaxServer` class is deployed as a Durable Object under the binding name `v1`.
|
|
23
|
+

|
|
41
24
|
|
|
42
25
|
### One action
|
|
43
26
|
|
|
27
|
+
`x(params, config)` builds the `ai` object. The first argument declares typed state keys with their initial values (write `x<Keys>()` when a key should start undefined), and the second argument is the service config.
|
|
28
|
+
|
|
29
|
+
<!-- prettier-ignore -->
|
|
44
30
|
```ts
|
|
45
|
-
|
|
31
|
+
// actions.ts
|
|
32
|
+
import { x } from 'ai-ax'
|
|
46
33
|
import { z } from 'zod'
|
|
47
34
|
|
|
48
|
-
export
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
35
|
+
export default x(
|
|
36
|
+
{ count: 0 },
|
|
37
|
+
{ name: 'counter', tree: 'count is the current value. Observe before acting', own: ({ sub }) => sub },
|
|
38
|
+
)
|
|
39
|
+
.describe('A counting app.', (a) =>
|
|
40
|
+
a
|
|
41
|
+
.action('increment', 'Add 1 to the current value', () => ++ai.count)
|
|
42
|
+
.action('overwrite', 'Overwrite the current value', { count: z.number() }, ({ count }) => (ai.count = count)),
|
|
43
|
+
)
|
|
44
|
+
.client('notify', 'Show an alert in the browser', { text: z.string() }, ({ text }) => alert(text))
|
|
45
|
+
.server('heavy', 'Run heavy work on the server', { src: z.string() }, () => import('./heavy'))
|
|
53
46
|
```
|
|
54
47
|
|
|
55
|
-
|
|
48
|
+
Each definition takes the tool name first, and after that the description, the zod schema and the implementation in any order — each part is recognized by its type, so an inline handler reads hono-like as `(name, schema, fn)` while a dynamic import reads `(name, () => import('./x'), description, schema)` with the schema at the end where the whole list scans best. The implementation is either an inline function or a dynamic import whose default export receives `(input, ai)`, so heavy or browser-incompatible code stays out of the other side's bundle. `.action` runs on whichever side called it, `.client` only in the browser, `.server` only in the Durable Object; when the other side calls it, the request travels over the shared doc and the result travels back, so a tool call from the LLM can open a dialog in the connected browser and return what the user did. `.describe(text, fn)` blocks nest, and their texts concatenate hierarchically into the MCP tool description. `get_tree` is built in — every service exposes the same observation tool, and `config.tree` replaces its description.
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
The declared names and schemas flow into the type of `ai`: `ai.overwrite({ count: 3 })` typechecks, `ai.overwrite(true)` and `ai.missing()` do not.
|
|
58
51
|
|
|
59
|
-
|
|
60
|
-
| -------- | -------------------------------- | ---------------------------------------------------------- |
|
|
61
|
-
| `both` | browser and Durable Object | executed locally on whichever side called it |
|
|
62
|
-
| `client` | browser only (needs browser API) | delegated to the connected browser via yjs act-req/act-res |
|
|
63
|
-
| `server` | Durable Object only | forwarded to the Durable Object |
|
|
52
|
+
### One tree
|
|
64
53
|
|
|
65
|
-
|
|
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.
|
|
66
55
|
|
|
67
|
-
The
|
|
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.
|
|
68
57
|
|
|
69
58
|
```ts
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
59
|
+
x(
|
|
60
|
+
{},
|
|
61
|
+
{
|
|
62
|
+
name: 'notes',
|
|
63
|
+
lobby: 'notes',
|
|
64
|
+
own: async ({ env, sub, path }) => (await env.my_bucket.head(`${sub}/${path}`))?.customMetadata?.site ?? '',
|
|
65
|
+
load: async ({ env, sub, path }) => (await env.my_bucket.get(`${sub}/${path}`))?.arrayBuffer(),
|
|
66
|
+
save: ({ env, sub, path, room }, buf) => env.my_bucket.put(`${sub}/${path}`, buf, { customMetadata: { site: room } }),
|
|
67
|
+
},
|
|
68
|
+
)
|
|
74
69
|
```
|
|
75
70
|
|
|
76
|
-
|
|
71
|
+
### One road
|
|
77
72
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Only two kinds of credentials exist, and both are jwt signed with `AUTH_SECRET`; a plain user id never crosses the network.
|
|
81
|
-
|
|
82
|
-
| path | credential | issued by | verified by |
|
|
83
|
-
| -------------------------- | ---------------------------- | ------------------------------------------ | --------------------------------------- |
|
|
84
|
-
| Browser → Worker | session cookie (Auth.js jwt) | `/api/auth` (Google OAuth) | `userSub()` |
|
|
85
|
-
| Claude → Worker `/api/mcp` | grant jwt `{ sub, path }` | llm middleware, minted after authorization | `readGrant()` |
|
|
86
|
-
| Worker → Durable Object | room name + `x-user-sub` | worker overwrites with authorized values | DO is reachable only via worker binding |
|
|
87
|
-
|
|
88
|
-
Authorization concentrates into a single function.
|
|
73
|
+
The app touches the library through the one `ai` object, and each runtime has exactly one adapter that receives it.
|
|
89
74
|
|
|
75
|
+
<!-- prettier-ignore -->
|
|
90
76
|
```ts
|
|
91
|
-
|
|
77
|
+
// index.ts (worker + Durable Object)
|
|
78
|
+
import { Hono } from 'hono'
|
|
79
|
+
import { hono } from 'ai-ax/hono'
|
|
80
|
+
import { PROMPT } from './prompt'
|
|
81
|
+
import ai from './actions'
|
|
82
|
+
|
|
83
|
+
export default new Hono().use('*', hono(ai, { system: PROMPT }))
|
|
84
|
+
export { Server as PartyServer } from 'ai-ax/server'
|
|
92
85
|
```
|
|
93
86
|
|
|
94
|
-
|
|
87
|
+
```tsx
|
|
88
|
+
// client.tsx (browser)
|
|
89
|
+
import { useClient } from 'ai-ax/react'
|
|
90
|
+
import ai from './actions'
|
|
95
91
|
|
|
96
|
-
|
|
92
|
+
const App = () => {
|
|
93
|
+
const { count } = useClient(ai)
|
|
94
|
+
return <button onClick={() => ai.increment()}>{count}</button>
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
97
|
|
|
98
|
-
|
|
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.
|
|
99
99
|
|
|
100
|
-
|
|
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.
|
|
101
101
|
|
|
102
|
-
`
|
|
102
|
+
`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
103
|
|
|
104
|
-
|
|
104
|
+
### One resolver
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
import { createStream } from 'ai-ax/client'
|
|
106
|
+
Authorization concentrates into the single `own` function: given `{ env, sub, path }` it answers which room this user may enter, and an empty string denies. Only two kinds of credentials exist, both jwt signed with `AUTH_SECRET` — a plain user id never crosses the network.
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
| path | credential | issued by | verified by |
|
|
109
|
+
| ----------------------- | ---------------------------- | -------------------------------------------- | ------------------------------------------- |
|
|
110
|
+
| Browser → Worker | session cookie (Auth.js jwt) | `/api/auth` (Google OAuth by default) | the Auth.js session |
|
|
111
|
+
| Claude → Worker MCP | grant jwt `{ sub, path }` | the LLM relay, minted per prompt | signature check before every tool execution |
|
|
112
|
+
| Worker → Durable Object | room name + user headers | the worker overwrites with authorized values | the DO is reachable only through the worker |
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
Browsers always connect to the alias room `my-room`, and the worker rewrites it to the room `own` returned, so no client ever picks a real room name. The grant jwt is minted when the user sends a prompt and is carried by the Claude API back on every MCP request; the LLM cannot choose the token's contents, and rewriting it breaks the signature, so at any moment the agent is sealed inside the one room that was just authorized, for at most `maxAge` seconds.
|
|
114
115
|
|
|
115
116
|
## How
|
|
116
117
|
|
|
117
|
-
| entry | main exports
|
|
118
|
-
| -------------- |
|
|
119
|
-
| `ai-ax` | `
|
|
120
|
-
| `ai-ax/
|
|
121
|
-
| `ai-ax/
|
|
122
|
-
| `ai-ax/
|
|
123
|
-
| `ai-ax/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
118
|
+
| entry | main exports | where it runs |
|
|
119
|
+
| -------------- | -------------------------------------------------------- | ----------------------- |
|
|
120
|
+
| `ai-ax` | `x` | everywhere (isomorphic) |
|
|
121
|
+
| `ai-ax/hono` | `hono` | worker |
|
|
122
|
+
| `ai-ax/server` | `Server`, `snapshot` | Durable Object |
|
|
123
|
+
| `ai-ax/client` | `client`, `hydrate`, `createStream`, `ALIAS` | browser |
|
|
124
|
+
| `ai-ax/react` | `useClient`, `Client`, `atom`, `setAtom`, `useAtomValue` | browser (React) |
|
|
125
|
+
| `ai-ax/const` | protocol constants (paths, header names) | both |
|
|
126
|
+
|
|
127
|
+
The worker expects a Durable Object binding named `v1` pointing at the exported `Server` class, and the environment needs `AUTH_SECRET`, the OAuth client credentials and `ANTHROPIC_API_KEY`. Config keys of `x(params, config)` and `hono(ai, extra)`:
|
|
128
|
+
|
|
129
|
+
| key | type | default | meaning |
|
|
130
|
+
| --------- | -------------------------- | ----------------------------------------- | ----------------------------------------------------- |
|
|
131
|
+
| name | `string` | `'x'` | service id, becomes the MCP server name |
|
|
132
|
+
| tree | `string` | generic text | description of the built-in `get_tree` tool |
|
|
133
|
+
| own | `(at: Peer) => string` | deny all | authorization resolver, `''` denies |
|
|
134
|
+
| adapter | `(env) => Adapter` | none | Auth.js adapter factory, jwt-only when omitted |
|
|
135
|
+
| lobby | `string` | none | one anonymous room without persistence |
|
|
136
|
+
| load | `(at: Saved) => buf` | none | snapshot restore on first doc load |
|
|
137
|
+
| save | `(at: Saved, buf) => void` | none | snapshot persist on debounce / alarm / disconnect |
|
|
138
|
+
| keys | `string[]` | whole doc | map names included in the snapshot |
|
|
139
|
+
| alias | `string` | `'my-room'` | connection name meaning "my room", rewritten by `own` |
|
|
140
|
+
| system | `string` | `''` | system prompt for the LLM |
|
|
141
|
+
| model | `string` | `'claude-sonnet-5'` | Claude API model id |
|
|
142
|
+
| claude | `string` | `'https://api.anthropic.com/v1/messages'` | Claude API URL, replaceable with an AI Gateway |
|
|
143
|
+
| maxTokens | `number` | `32000` | max_tokens per response |
|
|
144
|
+
| maxTurns | `number` | `10` | upper bound of pause_turn continuations |
|
|
145
|
+
| binding | `string` | `'v1'` | Durable Object binding name |
|
|
146
|
+
| host | `string` | request host | public host the Claude API reaches the MCP through |
|
|
147
|
+
| endpoint | `string` | `'/api/mcp'` | MCP endpoint path |
|
|
148
|
+
| relay | `string` | `'/api/llm'` | LLM relay path |
|
|
149
|
+
| version | `string` | `'1.0.0'` | MCP server version string |
|
|
150
|
+
| salt | `string` | `'authjs.mcp-token'` | grant jwt salt, separates services |
|
|
151
|
+
| maxAge | `number` | `43200` (12h) | grant jwt lifetime in seconds |
|
|
152
|
+
| cookie | `{ apex, services }` | none | services sharing a session cookie across subdomains |
|
|
153
|
+
| providers | `any[]` | Google | Auth.js providers |
|
|
146
154
|
|
|
147
155
|
The `x-user-sub` / `x-user-path` header names and the anthropic-version are internal protocol constants and intentionally not configurable, because both ends of each protocol must change together.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,49 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { ALIAS, BINDING } from "./const.js";
|
|
4
|
-
import * as Y from "yjs";
|
|
5
|
-
//#region src/browser/stream.d.ts
|
|
6
|
-
interface StreamTool {
|
|
7
|
-
server: string;
|
|
8
|
-
name: string;
|
|
9
|
-
}
|
|
10
|
-
interface StreamItem {
|
|
11
|
-
text: string;
|
|
12
|
-
tools: StreamTool[];
|
|
13
|
-
error?: boolean;
|
|
14
|
-
}
|
|
15
|
-
interface StreamSend {
|
|
16
|
-
prompt: string;
|
|
17
|
-
path?: string;
|
|
18
|
-
model?: string;
|
|
19
|
-
messages?: any[];
|
|
20
|
-
headers?: Record<string, string>;
|
|
21
|
-
}
|
|
22
|
-
interface StreamUpdate {
|
|
23
|
-
items: StreamItem[];
|
|
24
|
-
delta: string;
|
|
25
|
-
raw: string;
|
|
26
|
-
}
|
|
27
|
-
declare const createStream: (endpoint?: string) => {
|
|
28
|
-
parse: (raw: string) => StreamItem[];
|
|
29
|
-
send: (input: StreamSend, update?: (u: StreamUpdate) => void) => Promise<StreamItem[]>;
|
|
30
|
-
};
|
|
31
|
-
//#endregion
|
|
32
|
-
//#region src/client.d.ts
|
|
33
|
-
interface Route {
|
|
34
|
-
room?: string;
|
|
35
|
-
path?: string;
|
|
36
|
-
host?: string;
|
|
37
|
-
party?: string;
|
|
38
|
-
doc?: Y.Doc;
|
|
39
|
-
}
|
|
40
|
-
interface Link {
|
|
41
|
-
doc: Y.Doc;
|
|
42
|
-
call: Call;
|
|
43
|
-
synced: (fn: () => void) => void;
|
|
44
|
-
leave: () => void;
|
|
45
|
-
}
|
|
46
|
-
declare const onLeave: (fn: () => void) => undefined;
|
|
47
|
-
declare const connect: (route?: Route) => Link;
|
|
48
|
-
//#endregion
|
|
49
|
-
export { ALIAS, type ActMeta, type ActionDef, BINDING, Link, Route, type Side, type StreamItem, type StreamSend, type StreamTool, type StreamUpdate, action, actions, bind, connect, createStream, data, init, onLeave, side };
|
|
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 };
|
package/dist/client.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`)){if(r.startsWith(`[usage]`))continue;let e=
|
|
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};
|
package/dist/hono.d.ts
ADDED
package/dist/hono.js
ADDED
|
@@ -0,0 +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};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
import { Config, Own } from "./types.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
declare const ownSelf: () => Own;
|
|
5
|
-
//#endregion
|
|
6
|
-
//#region src/index.d.ts
|
|
7
|
-
declare const createAiax: (config: Config) => {
|
|
8
|
-
config: Config;
|
|
9
|
-
all: () => import("hono").MiddlewareHandler<any, string, {}, Response>;
|
|
10
|
-
auth: () => import("hono").MiddlewareHandler;
|
|
11
|
-
party: () => import("hono").MiddlewareHandler<any, string, {}, any>;
|
|
12
|
-
mcp: () => import("hono").MiddlewareHandler<any, string, {}, Response>;
|
|
13
|
-
llm: () => import("hono").MiddlewareHandler<any, string, {}, Response>;
|
|
14
|
-
};
|
|
15
|
-
//#endregion
|
|
16
|
-
export { createAiax, ownSelf, ownSite };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
`);u=f.pop()??``;for(let e of f){let c=w(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}},D=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 E(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 T(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`)},O=e=>r(async t=>{let n=await y(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(d(t),n,r))return t.text(`Forbidden`,403);let c=t.req.header(`x-original-host`)??t.req.header(`x-forwarded-host`)??new URL(t.req.url).host,l={key:d(t).ANTHROPIC_API_KEY,api:e.claude??`https://api.anthropic.com/v1/messages`,name:e.name,url:`https://${c}${e.endpoint??`/api/mcp`}`,model:a||e.model||`claude-sonnet-5`,system:e.system??``,maxTokens:e.maxTokens??32e3},u=()=>s?Promise.resolve(``):b(t,n,r,e),p=[...o,{role:`user`,content:i}],m=await T(l,await u(),p);return m.ok?f(t,t=>D(l,u,p,m,e=>t.write(e),e.maxTurns??10)):t.text(await m.text(),500)}),k=async(n,r,i,a,o)=>{let s=await h(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()},A=e=>r(async t=>{let r=await x(t,e),i=r?.sub??``,a=r?.path??``,o=e.lobby??``;if(r||!o){if(!i)return t.text(`Unauthorized`,401);if(o=await e.own(d(t),i,a),!o)return t.text(`Forbidden`,403)}let s=new m({name:e.name,version:e.version??`1.0.0`}),c=new p;return n.forEach((n,r)=>s.registerTool(r,{description:n.description,inputSchema:{...n.input}},async n=>{let s=await k(d(t)[e.binding??`v1`],o,{sub:i,path:a},r,n);return{content:[{type:`text`,text:JSON.stringify(s)}]}})),await s.connect(c),c.handleRequest(t)}),j=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),c=s.pathname.split(`/`);c[3]=encodeURIComponent(r),s.pathname=c.join(`/`);let l=new Request(s,new Request(n.req.raw,{headers:o}));return await g(l,d(n))??n.text(`Not Found`,404)},M=e=>r(async t=>{let n=decodeURIComponent(t.req.path.split(`/`)[3]??``),r=t.req.query(`path`)??``,i=await y(t);if(e.lobby&&n===e.lobby)return j(t,n,i,r);if(!i)return t.text(`Unauthorized`,401);let a=await e.own(d(t),i,r);return!a||n!==a&&n!==(e.alias??`my-room`)?t.text(`Forbidden`,403):j(t,a,i,r)}),N=e=>{let t={auth:v(e),party:M(e),mcp:A(e),llm:O(e)},n=r(async(n,r)=>{let i=()=>n.req.path.startsWith(`/parties/`)?t.party(n,r):n.req.path===(e.endpoint??`/api/mcp`)?t.mcp(n,r):n.req.path===`/api/llm`&&n.req.method===`POST`?t.llm(n,r):r(),a;if(await t.auth(n,async()=>void(a=await i())),a)return a});return{config:e,all:()=>n,auth:()=>t.auth,party:()=>t.party,mcp:()=>t.mcp,llm:()=>t.llm}};export{N as createAiax,C as ownSelf,S as ownSite};
|
|
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};
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { n as Route } from "./client-BN8W_6y5.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
//#region src/react.d.ts
|
|
4
|
+
declare const atom: <T>(initial: T) => {
|
|
5
|
+
value: T;
|
|
6
|
+
listeners: Set<(v: T) => void>;
|
|
7
|
+
};
|
|
8
|
+
type Atom<T> = ReturnType<typeof atom<T>>;
|
|
9
|
+
declare const setAtom: <T>(a: Atom<T>, v: T | ((p: T) => T)) => void;
|
|
10
|
+
declare const useAtomValue: <T>(a: Atom<T>) => T;
|
|
11
|
+
declare const useAtom: <T>(a: Atom<T>) => [T, (v: T) => void];
|
|
12
|
+
declare const AtomValue: <T>({ atom, children }: {
|
|
13
|
+
atom: Atom<T>;
|
|
14
|
+
children: (v: T) => ReactNode;
|
|
15
|
+
}) => ReactNode;
|
|
16
|
+
declare const useClient: <T extends object>(ai: T, route?: Route) => T;
|
|
17
|
+
declare const Client: <T extends object>({ ai, route, children }: {
|
|
18
|
+
ai: T;
|
|
19
|
+
route?: Route;
|
|
20
|
+
children: (ai: T) => ReactNode;
|
|
21
|
+
}) => ReactNode;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { Atom, AtomValue, Client, atom, setAtom, useAtom, useAtomValue, useClient };
|
package/dist/react.js
ADDED
|
@@ -0,0 +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};
|
package/dist/server.d.ts
CHANGED
|
@@ -1,14 +1,46 @@
|
|
|
1
|
-
import { Grant } from "./types.js";
|
|
2
|
-
import { Call } from "./
|
|
3
|
-
import
|
|
1
|
+
import { o as Grant, r as Config } from "./types-CQLXtCsz.js";
|
|
2
|
+
import { t as Call } from "./index-DNUoYl4_.js";
|
|
3
|
+
import * as Y from "yjs";
|
|
4
4
|
import { YServer } from "y-partyserver";
|
|
5
5
|
//#region src/server.d.ts
|
|
6
|
-
declare
|
|
6
|
+
declare const snapshot: (doc: Y.Doc, keys?: string[]) => Uint8Array;
|
|
7
|
+
declare class Server extends YServer {
|
|
8
|
+
static options: {
|
|
9
|
+
hibernate: boolean;
|
|
10
|
+
};
|
|
11
|
+
static callbackOptions: {
|
|
12
|
+
debounceWait: number;
|
|
13
|
+
debounceMaxWait: number;
|
|
14
|
+
};
|
|
15
|
+
env: any;
|
|
16
|
+
ctx: any;
|
|
17
|
+
ai: any;
|
|
18
|
+
sub: string;
|
|
19
|
+
path: string;
|
|
20
|
+
grant: Grant | null;
|
|
21
|
+
queue: Promise<any>;
|
|
22
|
+
hook: (run: () => Promise<any>) => Promise<any>;
|
|
7
23
|
wrap: (run: () => Promise<any>) => Promise<any>;
|
|
8
24
|
call: Call;
|
|
25
|
+
conf(): Config;
|
|
26
|
+
isLobby(): boolean;
|
|
27
|
+
saved(grant: Grant): {
|
|
28
|
+
env: any;
|
|
29
|
+
sub: string;
|
|
30
|
+
path: string;
|
|
31
|
+
room: string;
|
|
32
|
+
};
|
|
9
33
|
grantOf(request: Request): Grant;
|
|
10
|
-
|
|
34
|
+
owner(): Promise<Grant>;
|
|
35
|
+
remember(sub: string, path: string): Promise<void>;
|
|
36
|
+
onAct(grant: Grant, _request?: Request): Promise<void>;
|
|
37
|
+
onStart(): Promise<void>;
|
|
38
|
+
onConnect(conn: any, ctx: any): Promise<void>;
|
|
39
|
+
onLoad(): Promise<void>;
|
|
40
|
+
onSave(): Promise<void>;
|
|
41
|
+
onAlarm(): Promise<void>;
|
|
42
|
+
onClose(conn: any, code: number, reason: string, wasClean: boolean): Promise<void>;
|
|
11
43
|
onRequest(request: Request): Promise<Response>;
|
|
12
44
|
}
|
|
13
45
|
//#endregion
|
|
14
|
-
export {
|
|
46
|
+
export { Server, snapshot };
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
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};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,42 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type Side = 'server' | 'client' | 'both';
|
|
4
|
-
interface ActMeta {
|
|
5
|
-
name: string;
|
|
6
|
-
description: string;
|
|
7
|
-
input?: ZodRawShape;
|
|
8
|
-
}
|
|
9
|
-
interface ActionDef extends ActMeta {
|
|
10
|
-
side: Side;
|
|
11
|
-
run: (input: any) => Promise<any>;
|
|
12
|
-
}
|
|
13
|
-
interface Grant {
|
|
14
|
-
sub: string;
|
|
15
|
-
path: string;
|
|
16
|
-
}
|
|
17
|
-
type Own = (env: any, sub: string, path: string) => Promise<string> | string;
|
|
18
|
-
interface Cookie {
|
|
19
|
-
apex: string;
|
|
20
|
-
services: string[];
|
|
21
|
-
}
|
|
22
|
-
interface Config {
|
|
23
|
-
name: string;
|
|
24
|
-
own: Own;
|
|
25
|
-
database?: (env: any) => any;
|
|
26
|
-
lobby?: string;
|
|
27
|
-
alias?: string;
|
|
28
|
-
system?: string;
|
|
29
|
-
model?: string;
|
|
30
|
-
claude?: string;
|
|
31
|
-
maxTokens?: number;
|
|
32
|
-
maxTurns?: number;
|
|
33
|
-
binding?: string;
|
|
34
|
-
endpoint?: string;
|
|
35
|
-
version?: string;
|
|
36
|
-
salt?: string;
|
|
37
|
-
maxAge?: number;
|
|
38
|
-
cookie?: Cookie;
|
|
39
|
-
providers?: any[];
|
|
40
|
-
}
|
|
41
|
-
//#endregion
|
|
42
|
-
export { ActMeta, ActionDef, Config, Cookie, Grant, Own, Side };
|
|
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 };
|
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.5.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": [
|
|
@@ -30,10 +30,6 @@
|
|
|
30
30
|
"types": "./dist/index.d.ts",
|
|
31
31
|
"default": "./dist/index.js"
|
|
32
32
|
},
|
|
33
|
-
"./action": {
|
|
34
|
-
"types": "./dist/action.d.ts",
|
|
35
|
-
"default": "./dist/action.js"
|
|
36
|
-
},
|
|
37
33
|
"./client": {
|
|
38
34
|
"types": "./dist/client.d.ts",
|
|
39
35
|
"default": "./dist/client.js"
|
|
@@ -42,10 +38,22 @@
|
|
|
42
38
|
"types": "./dist/const.d.ts",
|
|
43
39
|
"default": "./dist/const.js"
|
|
44
40
|
},
|
|
41
|
+
"./hono": {
|
|
42
|
+
"types": "./dist/hono.d.ts",
|
|
43
|
+
"default": "./dist/hono.js"
|
|
44
|
+
},
|
|
45
|
+
"./react": {
|
|
46
|
+
"types": "./dist/react.d.ts",
|
|
47
|
+
"default": "./dist/react.js"
|
|
48
|
+
},
|
|
45
49
|
"./server": {
|
|
46
50
|
"types": "./dist/server.d.ts",
|
|
47
51
|
"default": "./dist/server.js"
|
|
48
52
|
},
|
|
53
|
+
"./types": {
|
|
54
|
+
"types": "./dist/types.d.ts",
|
|
55
|
+
"default": "./dist/types.js"
|
|
56
|
+
},
|
|
49
57
|
"./src": {
|
|
50
58
|
"types": "./src/index.ts",
|
|
51
59
|
"default": "./src/index.ts"
|
|
@@ -61,40 +69,48 @@
|
|
|
61
69
|
"README.md"
|
|
62
70
|
],
|
|
63
71
|
"scripts": {
|
|
64
|
-
"build": "tsdown --config ../../tsdown.config.ts",
|
|
72
|
+
"build": "tsdown --config ../../tsdown.config.ts && rm -f dist/*-*.d.ts",
|
|
65
73
|
"mmd": "for f in concept stack grant; do mmdc -i media/$f.mmd -o media/$f.svg; done"
|
|
66
74
|
},
|
|
67
75
|
"devDependencies": {
|
|
68
76
|
"@auth/core": "0.34.3",
|
|
69
|
-
"@auth/drizzle-adapter": "1.11.2",
|
|
70
77
|
"@hono/auth-js": "1.1.1",
|
|
71
78
|
"@hono/mcp": "0.3.1",
|
|
72
79
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
73
|
-
"
|
|
80
|
+
"@types/react": "^19.2.17",
|
|
74
81
|
"hono": "4.12.30",
|
|
75
82
|
"partyserver": "0.5.8",
|
|
83
|
+
"react": "^19.2.7",
|
|
76
84
|
"y-partyserver": "2.2.0",
|
|
77
85
|
"yjs": "13.6.31",
|
|
78
86
|
"zod": "4.4.3"
|
|
79
87
|
},
|
|
80
88
|
"peerDependencies": {
|
|
81
89
|
"@auth/core": ">=0.34.0",
|
|
82
|
-
"@auth/drizzle-adapter": ">=1.0.0",
|
|
83
90
|
"@hono/auth-js": ">=1.0.0",
|
|
84
91
|
"@hono/mcp": ">=0.3.0",
|
|
85
92
|
"@modelcontextprotocol/sdk": ">=1.0.0",
|
|
86
|
-
"drizzle-orm": ">=0.40.0",
|
|
87
93
|
"hono": ">=4.0.0",
|
|
88
94
|
"partyserver": ">=0.5.0",
|
|
95
|
+
"react": ">=18.0.0",
|
|
89
96
|
"y-partyserver": ">=2.0.0",
|
|
90
97
|
"yjs": ">=13.0.0",
|
|
91
98
|
"zod": ">=3.25.0"
|
|
92
99
|
},
|
|
93
100
|
"peerDependenciesMeta": {
|
|
94
|
-
"@auth
|
|
101
|
+
"@hono/auth-js": {
|
|
102
|
+
"optional": true
|
|
103
|
+
},
|
|
104
|
+
"@hono/mcp": {
|
|
105
|
+
"optional": true
|
|
106
|
+
},
|
|
107
|
+
"@modelcontextprotocol/sdk": {
|
|
108
|
+
"optional": true
|
|
109
|
+
},
|
|
110
|
+
"hono": {
|
|
95
111
|
"optional": true
|
|
96
112
|
},
|
|
97
|
-
"
|
|
113
|
+
"react": {
|
|
98
114
|
"optional": true
|
|
99
115
|
}
|
|
100
116
|
}
|
package/README.ja.md
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
# ai-ax
|
|
2
|
-
|
|
3
|
-
AI Agent Experience (AI/AX) library for Hono + Cloudflare.
|
|
4
|
-
|
|
5
|
-
ai-ax は、web アプリの「人間向けの体験 (UI/UX)」と「AI agent 向けの体験 (AI/AX)」を 1 つの操作面として実装するための library。操作は 1 度だけ宣言され、その宣言が人間には click handler、LLM には MCP tool として同時に機能する。両者は同じ realtime 状態を読み書きする。
|
|
6
|
-
|
|
7
|
-
library 全体を支える考えは 3 つ。**one action**、**one tree**、**one resolver**。
|
|
8
|
-
|
|
9
|
-
## Why
|
|
10
|
-
|
|
11
|
-
web は「操作するのは人間」という前提で UI/UX を積み上げてきた。そこへ後から LLM の操作経路を足すと、操作面が 2 つ、認証認可が 2 系統、バグも 2 倍になる。2 つの実装は必ずずれていき、そのずれは数週間後の log でようやく見つかる。
|
|
12
|
-
|
|
13
|
-
ai-ax は 2 つ目の実装を管理するのではなく、消す。AI agent をアプリのユーザーの一人として扱い、画面を見る人間と同じ状態を読ませ、同じ操作を呼ばせる。開発者が UI のボタンを click して debug すれば、その debug 済みコードがそのまま MCP 経由で agent が実行するコードになる。ずれていく「もう 1 つの実装」は存在しない。
|
|
14
|
-
|
|
15
|
-

|
|
16
|
-
|
|
17
|
-
**one action** — 操作の最小単位を 1 度だけ宣言し、UI と MCP の両方から同じ実装に到達させる。**one tree** — 全状態は単一の yjs の木であり、人間には描画された画面として、LLM には `get_tree` の観測結果として同じ木が見える。**one resolver** — 認可は「誰がどの部屋に入れるか」を決める 1 つの関数に集約され、人間も AI もすべての経路がそこを通る。
|
|
18
|
-
|
|
19
|
-
## What
|
|
20
|
-
|
|
21
|
-
前提となる構成は Hono + Cloudflare Workers。realtime 同期は partyserver (Durable Objects) + yjs、認証は Auth.js、LLM は Claude API の MCP connector。ai-ax はその接続部 — 認証、認可、realtime 同期、MCP server、LLM 中継 — を Hono middleware として提供する。
|
|
22
|
-
|
|
23
|
-

|
|
24
|
-
|
|
25
|
-
worker 側は合成 middleware を 1 つ置くだけでよく、Durable Object は re-export で済む。
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
import { Hono } from 'hono'
|
|
29
|
-
import { createAiax, ownSelf } from 'ai-ax'
|
|
30
|
-
import { PROMPT } from './prompt'
|
|
31
|
-
import './actions'
|
|
32
|
-
|
|
33
|
-
const ai = createAiax({ name: 'demo', own: ownSelf(), database: (e) => e.my_d1, system: PROMPT })
|
|
34
|
-
const app = new Hono().use('*', ai.all())
|
|
35
|
-
|
|
36
|
-
export default app
|
|
37
|
-
export { AiaxServer } from 'ai-ax/server'
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
`ai.all()` は `/parties/*` を websocket 同期へ、`/api/mcp` を MCP server へ、`POST /api/llm` を Claude 中継へ振り分け、Auth.js の session も処理する。それ以外はすべて自作の route へ素通しする。export した `AiaxServer` は binding 名 `v1` の Durable Object として deploy する。
|
|
41
|
-
|
|
42
|
-
### one action
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
import { action } from 'ai-ax/action'
|
|
46
|
-
import { z } from 'zod'
|
|
47
|
-
|
|
48
|
-
export const placeMark = action<{ cell: number }, string>(() => import('./actions/place-mark'), 'both', {
|
|
49
|
-
name: 'place_mark',
|
|
50
|
-
description: '空いているマスに現在の手番の印を置く',
|
|
51
|
-
input: { cell: z.number().describe('マス番号 0-8') },
|
|
52
|
-
})
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
この 1 つの宣言から 2 つのものが生まれる。UI の event handler から呼べる async 関数と、LLM に公開される MCP tool (name / description / zod input schema)。第 1 引数は dynamic import でも実装関数そのものでもよく、bundle を分けたい service にも 1 file で書きたいアプリにも合う。
|
|
56
|
-
|
|
57
|
-
`side` は「その実装がどこでなら動けるか」を表し、反対側から呼ばれたときは ai-ax が境界を越えて実行を届ける。
|
|
58
|
-
|
|
59
|
-
| side | 動作場所 | 反対側から呼ばれたとき |
|
|
60
|
-
| -------- | --------------------------------- | -------------------------------------------------- |
|
|
61
|
-
| `both` | browser と Durable Object の両方 | 呼んだ側でそのまま実行 |
|
|
62
|
-
| `client` | browser のみ (browser API が必要) | yjs 上の act-req / act-res で接続中 browser へ委譲 |
|
|
63
|
-
| `server` | Durable Object のみ | Durable Object へ転送 |
|
|
64
|
-
|
|
65
|
-
action の中では `data()` が共有の `Y.Map` (`tree`) を返す。ここへの書き込みは接続中の全 browser に realtime 同期され、LLM が観測するのも全く同じ木。だから agent の操作は人間の画面にその場で現れる。
|
|
66
|
-
|
|
67
|
-
browser 側は 1 度接続すればすべてが動き出す。
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
import { ALIAS, connect } from 'ai-ax/client'
|
|
71
|
-
|
|
72
|
-
const link = connect({ room: ALIAS })
|
|
73
|
-
link.synced(() => boot())
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
client は常に別名 `my-room` で接続する。server が認可済みの room 名へ書き換えるため、client が実際の room 名を選ぶことはない。
|
|
77
|
-
|
|
78
|
-
### one resolver
|
|
79
|
-
|
|
80
|
-
credential は 2 種類だけ。どちらも `AUTH_SECRET` で署名された jwt であり、平文の user id が network を渡ることはない。
|
|
81
|
-
|
|
82
|
-
| 経路 | credential | 発行 | 検証 |
|
|
83
|
-
| -------------------------- | ---------------------------- | --------------------------------- | ------------------------------------- |
|
|
84
|
-
| Browser → Worker | session cookie (Auth.js jwt) | `/api/auth` (Google OAuth) | `userSub()` |
|
|
85
|
-
| Claude → Worker `/api/mcp` | grant jwt `{ sub, path }` | llm middleware が認可後に発行 | `readGrant()` |
|
|
86
|
-
| Worker → Durable Object | room 名 + `x-user-sub` | worker が認可済みの値で必ず上書き | DO へは worker binding 経由でのみ到達 |
|
|
87
|
-
|
|
88
|
-
認可は 1 つの関数に集約される。
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
type Own = (env: any, sub: string, path: string) => Promise<string> | string
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
答えるのは「この user はこの path に対してどの部屋を得るか」だけで、空文字は拒否。`ownSelf()` は user 本人の id を返す (個人アプリ: 全員が自分だけの部屋を持つ)。`ownSite(bucket)` は R2 の file metadata を引く (editor アプリ: file を所有していればその部屋が開く)。D1 の所属 table を join する resolver も同じ型で書ける。
|
|
95
|
-
|
|
96
|
-
grant jwt は llm middleware が turn ごとに発行し、Claude API がすべての MCP request に載せて `/api/mcp` へ運ぶ。MCP server は毎回署名を検証してから操作を実行する。LLM は token の中身を選べず、書き換えれば署名が壊れる。つまり agent はその瞬間に認可された 1 部屋の中に封じられる。
|
|
97
|
-
|
|
98
|
-

|
|
99
|
-
|
|
100
|
-
### LLM 中継
|
|
101
|
-
|
|
102
|
-
`POST /api/llm` に `{ path, prompt, model?, messages? }` を送ると、認可 → grant 発行 → MCP server 付きで Claude API 呼び出し → text stream 中継、が 1 本で流れる。`messages` は prompt の前に積む過去の会話。library は履歴を保存しない — 何をどこへどんな形で永続化するかはアプリの決めごとであり、ここに渡されたものをそのまま再生するだけ。
|
|
103
|
-
|
|
104
|
-
stream は素の text で、`[tool] server name` の行が tool 呼び出しを、末尾の `[usage] ...` の行が turn の終わりを表す。この wire format の browser 側の相棒が `createStream()`。
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
import { createStream } from 'ai-ax/client'
|
|
108
|
-
|
|
109
|
-
const stream = createStream()
|
|
110
|
-
const items = await stream.send({ prompt, path, model, messages }, (u) => draw(u.items))
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
`parse` は raw text を `{ text, tools }` の item 列に分解して usage を隠し、`send` は POST しながら parse 済みの update を流す。対になる `pack` / `unpack` は完成した応答を保存可能な `{ reply, tools }` 形式と相互変換する。アプリは wire format そのものに触れずに、会話の描画・永続化・再生ができる。
|
|
114
|
-
|
|
115
|
-
## How
|
|
116
|
-
|
|
117
|
-
| entry | 主な export | 動作場所 |
|
|
118
|
-
| -------------- | ----------------------------------- | ------------------------ |
|
|
119
|
-
| `ai-ax` | `createAiax`, `ownSelf`, `ownSite` | worker (Hono middleware) |
|
|
120
|
-
| `ai-ax/client` | `connect`, `createStream`, `ALIAS` | browser |
|
|
121
|
-
| `ai-ax/action` | `action`, `actions`, `data`, `side` | 共有の action 宣言 |
|
|
122
|
-
| `ai-ax/server` | `AiaxServer` | Durable Object |
|
|
123
|
-
| `ai-ax/const` | protocol 定数 (path と header 名) | 両方 |
|
|
124
|
-
|
|
125
|
-
`createAiax(config)` は以下を受け取り `{ all, auth, party, mcp, llm }` を返す。`all()` が上で示した合成 middleware で、4 つの factory は個別にも使える。
|
|
126
|
-
|
|
127
|
-
| key | 型 | 既定値 | 説明 |
|
|
128
|
-
| --------- | --------------------- | ----------------------------------------- | ----------------------------------------------------------- |
|
|
129
|
-
| name | `string` | 必須 | service id。MCP server 名になる |
|
|
130
|
-
| own | `Own` | 必須 | 認可 resolver。`''` で拒否 |
|
|
131
|
-
| database | `(env) => D1Database` | なし | Auth.js DrizzleAdapter に渡す D1。省略時は adapter なし jwt |
|
|
132
|
-
| lobby | `string` | なし | 匿名で開放する demo 部屋 1 つ。省略時は全経路で認証必須 |
|
|
133
|
-
| alias | `string` | `'my-room'` | 「自分の部屋」を指す接続名。`own` の結果へ書き換えられる |
|
|
134
|
-
| system | `string` | `''` | LLM の system prompt |
|
|
135
|
-
| model | `string` | `'claude-sonnet-5'` | Claude API の model id |
|
|
136
|
-
| claude | `string` | `'https://api.anthropic.com/v1/messages'` | Claude API の URL。AI Gateway 経由に差し替え可 |
|
|
137
|
-
| maxTokens | `number` | `32000` | 1 応答の max_tokens |
|
|
138
|
-
| maxTurns | `number` | `10` | pause_turn 継続の上限回数 |
|
|
139
|
-
| binding | `string` | `'v1'` | Durable Object binding 名 |
|
|
140
|
-
| endpoint | `string` | `'/api/mcp'` | MCP endpoint の path |
|
|
141
|
-
| version | `string` | `'1.0.0'` | MCP server の version 表記 |
|
|
142
|
-
| salt | `string` | `'authjs.mcp-token'` | grant jwt の salt。service ごとの分離 |
|
|
143
|
-
| maxAge | `number` | `43200` (12h) | grant jwt の有効秒数 |
|
|
144
|
-
| cookie | `{ apex, services }` | なし | subdomain 間で session cookie を共有する service 群 |
|
|
145
|
-
| providers | `any[]` | Google | Auth.js providers の差し替え |
|
|
146
|
-
|
|
147
|
-
`x-user-sub` / `x-user-path` の header 名と anthropic-version は library 内部の protocol 定数であり、意図的に設定不可。protocol の両端は同時に変わる必要があるため。
|
package/dist/action.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ActMeta, ActionDef, Side } from "./types.js";
|
|
2
|
-
import * as Y from "yjs";
|
|
3
|
-
//#region src/action.d.ts
|
|
4
|
-
type Action<I, R> = (input: I) => R | Promise<R>;
|
|
5
|
-
type Call = (name: string, input: any) => Promise<any>;
|
|
6
|
-
declare const actions: Map<string, ActionDef>;
|
|
7
|
-
declare const side: Side;
|
|
8
|
-
declare const bind: (doc: Y.Doc) => Y.Doc;
|
|
9
|
-
declare const data: <T = any>(name?: string) => Y.Map<T>;
|
|
10
|
-
declare function action<I extends object, R>(load: Action<I, R> | ((input?: I) => any), at: Side, meta: ActMeta): (input: I) => Promise<R>;
|
|
11
|
-
declare const init: (doc: Y.Doc, wrap?: (run: () => Promise<any>) => Promise<any>) => Call;
|
|
12
|
-
//#endregion
|
|
13
|
-
export { Call, action, actions, bind, data, init, side };
|
package/dist/action.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e=new Map,t=typeof window>`u`?`server`:`client`;let n,r;const i=e=>r=e,a=(e=`tree`)=>r.getMap(e);function o(t,r,i){let a=async e=>{let n=await t(e);return typeof n==`function`?n(e):n?.default?n.default(e):n};return e.set(i.name,{...i,side:r,run:a}),e=>n(i.name,e)}const s=(r,a=e=>e())=>{i(r);let o=r.getMap(`act-req`),s=r.getMap(`act-res`),c=new Map,l=async n=>{let c=o.get(n),l=e.get(c?.name);!l||l.side!==t||(o.delete(n),s.set(n,{value:await a(()=>(i(r),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)),n=(n,a)=>{let s=e.get(n);return s.side===t||s.side===`both`?(i(r),s.run(a)):new Promise(e=>{let t=crypto.randomUUID();c.set(t,e),o.set(t,{name:n,input:a})})},n};export{o as action,e as actions,i as bind,a as data,s as init,t as side};
|