@abide/abide 0.124.1 → 0.125.1
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/ABIDE.md +4 -0
- package/CHANGELOG.md +50 -0
- package/bin/abide.ts +3 -2
- package/docs/REGISTRY.md +67 -40
- package/docs/RULEBOOK.md +206 -18
- package/docs/abide.md +393 -67
- package/package.json +1 -1
- package/src/compiler/appMap.ts +14 -1
- package/src/compiler/compileApp.ts +1 -0
- package/src/compiler/declaredExports.ts +41 -0
- package/src/compiler/handlersUnder.ts +1 -0
- package/src/compiler/hookCalls.ts +1 -0
- package/src/compiler/projectShadow.ts +52 -2
- package/src/compiler/readsIn.ts +39 -34
- package/src/server/PageRender.ts +7 -8
- package/src/server/abideModules.ts +22 -9
- package/src/server/addressed.ts +14 -0
- package/src/server/answerRequest.ts +115 -52
- package/src/server/answerSocket.ts +6 -5
- package/src/server/answerWorker.ts +34 -23
- package/src/server/assistant.ts +141 -35
- package/src/server/banner.ts +9 -5
- package/src/server/bridgeRelay.ts +3 -3
- package/src/server/callerOf.ts +5 -0
- package/src/server/claudeCode.ts +20 -11
- package/src/server/commands.ts +14 -7
- package/src/server/createApp.ts +65 -12
- package/src/server/devWorker.ts +18 -16
- package/src/server/faceOf.ts +3 -1
- package/src/server/generatedSurfaces.ts +6 -5
- package/src/server/handlerFileMount.ts +2 -0
- package/src/server/hooks.ts +54 -8
- package/src/server/index.ts +21 -4
- package/src/server/invokeHandler.ts +1 -1
- package/src/server/mountHandlers.ts +9 -7
- package/src/server/principal.ts +3 -2
- package/src/server/render.ts +11 -2
- package/src/server/requestScope.ts +61 -1
- package/src/server/responseRelay.ts +9 -3
- package/src/server/serveDev.ts +18 -3
- package/src/server/sqliteStore.ts +65 -0
- package/src/server/testMount.ts +1 -0
- package/src/shared/AssistantEvent.ts +40 -0
- package/src/shared/BrowserHooks.ts +73 -0
- package/src/shared/CURRENT_ROUTER.ts +3 -1
- package/src/shared/Issues.ts +11 -3
- package/src/shared/Middleware.ts +14 -7
- package/src/shared/UNEXPECTED.ts +1 -1
- package/src/shared/answerOf.ts +9 -4
- package/src/shared/imagePiece.ts +85 -0
- package/src/shared/index.ts +17 -0
- package/src/shared/isServer.ts +15 -0
- package/src/shared/navigate.ts +10 -3
- package/src/shared/onError.ts +51 -0
- package/src/shared/onRefusal.ts +47 -0
- package/src/shared/online.ts +5 -1
- package/src/shared/reactive/Keyed.ts +12 -1
- package/src/shared/reactive/Reactive.ts +53 -25
- package/src/shared/reactive/cellShape.ts +4 -3
- package/src/shared/reactive/poll.ts +49 -0
- package/src/shared/reactive/scoped.ts +10 -1
- package/src/shared/reactive/scoping.ts +14 -0
- package/src/shared/reactive/watch.ts +19 -8
- package/src/shared/resultsOf.ts +23 -0
- package/src/shared/route.ts +18 -4
- package/src/shared/rpc.ts +125 -30
- package/src/shared/seededDocument.ts +5 -6
- package/src/shared/storedValue.ts +32 -0
- package/src/shared/webStore.ts +54 -0
- package/src/ui/Router.ts +42 -9
- package/src/ui/outboxQueue.ts +1 -1
- package/src/ui/wire.ts +4 -4
- package/src/shared/KEEP_OPENED.ts +0 -7
- /package/src/{server → shared}/IMAGE_TYPES.ts +0 -0
package/src/server/claudeCode.ts
CHANGED
|
@@ -8,17 +8,15 @@
|
|
|
8
8
|
// directory, settings — is not an option here at all, and its built-in `tools` are a request the visitor
|
|
9
9
|
// grants or does not: the bridge decides (44.15, 44.16, 44.29).
|
|
10
10
|
|
|
11
|
+
import type {
|
|
12
|
+
AssistantContent,
|
|
13
|
+
AssistantMessage,
|
|
14
|
+
} from '#shared/AssistantEvent.ts'
|
|
11
15
|
import { BRIDGE_SCHEME } from '#shared/BRIDGE_SCHEME.ts'
|
|
12
16
|
import { mounted } from '#shared/mount.ts'
|
|
13
17
|
import { refuse } from '#shared/refuse.ts'
|
|
14
18
|
|
|
15
|
-
import {
|
|
16
|
-
type AssistantContent,
|
|
17
|
-
type AssistantMessage,
|
|
18
|
-
blocksOf,
|
|
19
|
-
forwardOf,
|
|
20
|
-
type Provider,
|
|
21
|
-
} from './assistant.ts'
|
|
19
|
+
import { blocksOf, forwardOf, type Provider } from './assistant.ts'
|
|
22
20
|
import { BRIDGE_VERSION } from './BRIDGE_VERSION.ts'
|
|
23
21
|
import { mintCode, pairingStatus, runTurn } from './bridgeRelay.ts'
|
|
24
22
|
import { callerOf } from './callerOf.ts'
|
|
@@ -45,12 +43,13 @@ type PairingOptions = Pick<ClaudeCodeConfig, 'tools'>
|
|
|
45
43
|
|
|
46
44
|
/**
|
|
47
45
|
* Whether this browser's bridge is connected, and the command and the link that pair one where it is
|
|
48
|
-
* not.
|
|
46
|
+
* not, with the command that installs the app the link opens.
|
|
49
47
|
*/
|
|
50
48
|
export type BridgeStatus = {
|
|
51
49
|
paired: boolean
|
|
52
50
|
command: string | undefined
|
|
53
51
|
link: string | undefined
|
|
52
|
+
install: string | undefined
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
/** The visitor's own Claude Code, reached through the bridge paired to their browser. */
|
|
@@ -123,17 +122,21 @@ function askingCaller(): string {
|
|
|
123
122
|
function bridgeCommand(options: PairingOptions = {}): {
|
|
124
123
|
command: string
|
|
125
124
|
link: string
|
|
125
|
+
install: string
|
|
126
126
|
expires: number
|
|
127
127
|
} {
|
|
128
128
|
const { code, expires } = mintCode(askingCaller(), signedInAs())
|
|
129
129
|
return { ...commandFor(code, options), expires }
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* The command and the link that pair a bridge by `code`, granting `tools`, and the command that
|
|
134
|
+
* installs the app the link opens — of the release the command runs, 44.56.
|
|
135
|
+
*/
|
|
133
136
|
function commandFor(
|
|
134
137
|
code: string,
|
|
135
138
|
{ tools }: PairingOptions,
|
|
136
|
-
): { command: string; link: string } {
|
|
139
|
+
): { command: string; link: string; install: string } {
|
|
137
140
|
const site = `${appOrigin() ?? new URL(request().url).origin}${mounted('')}`
|
|
138
141
|
const granted = tools?.length ? tools.join(',') : undefined
|
|
139
142
|
const query = new URLSearchParams({ site, code, version: BRIDGE_VERSION })
|
|
@@ -142,6 +145,7 @@ function commandFor(
|
|
|
142
145
|
return {
|
|
143
146
|
command: `bunx @abide/bridge@${BRIDGE_VERSION} ${site} ${code}${grant}`,
|
|
144
147
|
link: `${BRIDGE_SCHEME}://pair?${query}`,
|
|
148
|
+
install: `bunx @abide/bridge@${BRIDGE_VERSION} install`,
|
|
145
149
|
}
|
|
146
150
|
}
|
|
147
151
|
|
|
@@ -159,7 +163,12 @@ async function* bridge(
|
|
|
159
163
|
request().signal,
|
|
160
164
|
))
|
|
161
165
|
yield code === undefined
|
|
162
|
-
? {
|
|
166
|
+
? {
|
|
167
|
+
paired: true,
|
|
168
|
+
command: undefined,
|
|
169
|
+
link: undefined,
|
|
170
|
+
install: undefined,
|
|
171
|
+
}
|
|
163
172
|
: { paired: false, ...commandFor(code, options) }
|
|
164
173
|
}
|
|
165
174
|
|
package/src/server/commands.ts
CHANGED
|
@@ -245,10 +245,11 @@ function usage(host: Host, commands: Row[]): string {
|
|
|
245
245
|
text += ` ${command.padEnd(width)} ${meaning}\n`
|
|
246
246
|
if (!sourced)
|
|
247
247
|
return `${text}
|
|
248
|
-
--port
|
|
249
|
-
--
|
|
250
|
-
--
|
|
251
|
-
--
|
|
248
|
+
--port the listen port the app is served on
|
|
249
|
+
--hostname the interface it listens on — every one where none is named
|
|
250
|
+
--url the running app the shell, call, watch, openapi and mcp talk to
|
|
251
|
+
--method the method call sends, where the handler answers more than one
|
|
252
|
+
--version the version of this executable's app
|
|
252
253
|
`
|
|
253
254
|
text += `
|
|
254
255
|
--root the app's source directory, where its pages sit —
|
|
@@ -256,6 +257,7 @@ function usage(host: Host, commands: Row[]): string {
|
|
|
256
257
|
--out where the build goes, beside the app by default; the file compile and
|
|
257
258
|
bundle write — named after the app, where you stand
|
|
258
259
|
--port the listen port for dev, start and the shell's app
|
|
260
|
+
--hostname the interface they listen on — every one where none is named
|
|
259
261
|
--url the running app the shell, call, watch, openapi and mcp talk to
|
|
260
262
|
--target the platform compile builds for — linux-x64, darwin-arm64, windows-x64…
|
|
261
263
|
--platforms several of them, comma-separated, one executable each
|
|
@@ -291,6 +293,8 @@ function parseFlags(argv: string[]) {
|
|
|
291
293
|
// `PORT` and then falls back to 3000 — `abide dev` scanning on from there, 38.63 — and
|
|
292
294
|
// a default written here would take that away from every host that sets it.
|
|
293
295
|
port: { type: 'string' },
|
|
296
|
+
// NO DEFAULT EITHER: `Bun.serve` given none binds every interface (38.136).
|
|
297
|
+
hostname: { type: 'string' },
|
|
294
298
|
url: { type: 'string' },
|
|
295
299
|
method: { type: 'string' },
|
|
296
300
|
target: { type: 'string' },
|
|
@@ -321,14 +325,17 @@ async function served(
|
|
|
321
325
|
process.env.NODE_ENV ??= 'production'
|
|
322
326
|
const booted = await booting(host, app, given)
|
|
323
327
|
if (typeof booted === 'number') return booted
|
|
324
|
-
const port = given.values
|
|
325
|
-
await booted.listen(
|
|
328
|
+
const { port, hostname } = given.values
|
|
329
|
+
await booted.listen({
|
|
330
|
+
...(port === undefined ? {} : { port: Number(port) }),
|
|
331
|
+
...(hostname === undefined ? {} : { hostname }),
|
|
332
|
+
})
|
|
326
333
|
stopOnSignalOrFault(booted)
|
|
327
334
|
const server = LISTENING.server as Bun.Server<unknown>
|
|
328
335
|
return {
|
|
329
336
|
app: booted,
|
|
330
337
|
url: withoutTrailingSlash(server.url.href),
|
|
331
|
-
heading: banner(host.name, 'start', app.package, server),
|
|
338
|
+
heading: banner(host.name, 'start', app.package, server, hostname),
|
|
332
339
|
}
|
|
333
340
|
}
|
|
334
341
|
|
package/src/server/createApp.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { PRINCIPAL_HEADER } from '#shared/PRINCIPAL_HEADER.ts'
|
|
|
17
17
|
import { RPC_SERVER } from '#shared/RPC_SERVER.ts'
|
|
18
18
|
|
|
19
19
|
import { laneFailure } from './answerFailure.ts'
|
|
20
|
-
import { answerRequest, type AppSources } from './answerRequest.ts'
|
|
20
|
+
import { answerRequest, type AppSources, laneOf } from './answerRequest.ts'
|
|
21
21
|
import { Mux, type MuxConnection } from './answerSocket.ts'
|
|
22
22
|
import {
|
|
23
23
|
type Builder,
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
spawnedBuild,
|
|
27
27
|
} from './appBuild.ts'
|
|
28
28
|
import { holdsApp } from './appTree.ts'
|
|
29
|
+
import { presumeCaller } from './callerOf.ts'
|
|
29
30
|
import {
|
|
30
31
|
APP_PACKAGE,
|
|
31
32
|
type AppPackage,
|
|
@@ -35,7 +36,14 @@ import {
|
|
|
35
36
|
} from './config.ts'
|
|
36
37
|
import { Followers } from './Followers.ts'
|
|
37
38
|
import { McpSurface } from './generatedSurfaces.ts'
|
|
38
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
type AppContext,
|
|
41
|
+
HOOKS,
|
|
42
|
+
inOrder,
|
|
43
|
+
type Lane,
|
|
44
|
+
reportUnexpected,
|
|
45
|
+
rootRungs,
|
|
46
|
+
} from './hooks.ts'
|
|
39
47
|
import { IDLE_TIMEOUT_SECONDS } from './IDLE_TIMEOUT_SECONDS.ts'
|
|
40
48
|
import { installServer } from './installServer.ts'
|
|
41
49
|
import { followedIn, type Mount } from './mountHandlers.ts'
|
|
@@ -51,6 +59,7 @@ import {
|
|
|
51
59
|
holdScope,
|
|
52
60
|
inReleasedScope,
|
|
53
61
|
LISTENING,
|
|
62
|
+
RECEIVED_FROM,
|
|
54
63
|
rungsTook,
|
|
55
64
|
serverTiming,
|
|
56
65
|
} from './requestScope.ts'
|
|
@@ -65,12 +74,15 @@ const REQUEST_LOG = namedLogger('abide:request')
|
|
|
65
74
|
// by it.
|
|
66
75
|
const STOP_TIMEOUT = 5000
|
|
67
76
|
|
|
77
|
+
/** Who a scope `App.run` or `App.fetch` opens is answered as, over what its request's cookies say. */
|
|
78
|
+
type Presumed = { principal?: Record<string, unknown>; caller?: string }
|
|
79
|
+
|
|
68
80
|
/** The app object: what a host, a test or a script holds the app as. */
|
|
69
81
|
export interface App {
|
|
70
|
-
fetch(request: Request): Promise<Response>
|
|
82
|
+
fetch(request: Request, options?: Presumed): Promise<Response>
|
|
71
83
|
run<Result>(
|
|
72
84
|
fn: () => Result | Promise<Result>,
|
|
73
|
-
options?: { request?: Request
|
|
85
|
+
options?: { request?: Request } & Presumed,
|
|
74
86
|
): Promise<Result>
|
|
75
87
|
listen(options?: {
|
|
76
88
|
port?: number
|
|
@@ -247,8 +259,15 @@ export async function bootEntered(
|
|
|
247
259
|
// route's rungs, the ones below it running after routing (37.2, `answerRequest`). 21.20 — the
|
|
248
260
|
// center takes its own time back out of what `fetch` counts for the whole onion.
|
|
249
261
|
// 37.14 — before routing, the lane is the request's to say (15.22).
|
|
250
|
-
const rungs = rootRungs(
|
|
251
|
-
|
|
262
|
+
const rungs = rootRungs(
|
|
263
|
+
(failure, request) =>
|
|
264
|
+
laneFailure(
|
|
265
|
+
booted,
|
|
266
|
+
new URL(request.url).pathname,
|
|
267
|
+
failure,
|
|
268
|
+
request,
|
|
269
|
+
),
|
|
270
|
+
(request) => laneOf(booted, request),
|
|
252
271
|
)
|
|
253
272
|
const answer = compose(rungs, async ({ request }) => {
|
|
254
273
|
const began = performance.now()
|
|
@@ -265,23 +284,32 @@ export async function bootEntered(
|
|
|
265
284
|
let stopping = false
|
|
266
285
|
let bound: Bun.Server<unknown> | undefined
|
|
267
286
|
|
|
287
|
+
// 42.19, 42.20, 42.21 — inside the scope, ahead of anything that reads either.
|
|
288
|
+
const presume = (options: Presumed | undefined): void => {
|
|
289
|
+
if (options?.principal !== undefined)
|
|
290
|
+
presumePrincipal(options.principal)
|
|
291
|
+
if (options?.caller !== undefined) presumeCaller(options.caller)
|
|
292
|
+
}
|
|
293
|
+
|
|
268
294
|
const run: App['run'] = async (fn, options) => {
|
|
269
|
-
|
|
270
|
-
//
|
|
295
|
+
// 42.19, 42.21 — a principal or a caller is a request's, so one given alone is given a request
|
|
296
|
+
// to be.
|
|
271
297
|
const request =
|
|
272
298
|
options?.request ??
|
|
273
|
-
(
|
|
299
|
+
(options?.principal === undefined && options?.caller === undefined
|
|
300
|
+
? undefined
|
|
301
|
+
: new Request(IN_PROCESS_ORIGIN))
|
|
274
302
|
return inReleasedScope(
|
|
275
303
|
request,
|
|
276
304
|
async () => {
|
|
277
|
-
|
|
305
|
+
presume(options)
|
|
278
306
|
return await fn()
|
|
279
307
|
},
|
|
280
308
|
answering,
|
|
281
309
|
)
|
|
282
310
|
}
|
|
283
311
|
|
|
284
|
-
const fetch = async (given
|
|
312
|
+
const fetch: App['fetch'] = async (given, options) => {
|
|
285
313
|
if (stopping) return plainAnswer('the app is stopping', 503)
|
|
286
314
|
const request = belowMount(given)
|
|
287
315
|
if (request instanceof Response) return request
|
|
@@ -294,9 +322,12 @@ export async function bootEntered(
|
|
|
294
322
|
return await inReleasedScope(
|
|
295
323
|
request,
|
|
296
324
|
async () => {
|
|
325
|
+
presume(options)
|
|
297
326
|
let answered: Response
|
|
298
327
|
try {
|
|
299
|
-
answered = await answer(
|
|
328
|
+
answered = await answer(
|
|
329
|
+
new RootContext(booted, request),
|
|
330
|
+
)
|
|
300
331
|
if (rungs.length > 0)
|
|
301
332
|
rungsTook(performance.now() - started)
|
|
302
333
|
} catch (thrown) {
|
|
@@ -431,6 +462,8 @@ export async function bootEntered(
|
|
|
431
462
|
maxRequestBodySize: Number.MAX_SAFE_INTEGER,
|
|
432
463
|
// 18.1, 37.3 — THE ONE MUX, upgraded where the app lane admitted it.
|
|
433
464
|
fetch: async (request, server) => {
|
|
465
|
+
// 22.12 — where it came from, for `requestIP` to answer.
|
|
466
|
+
RECEIVED_FROM.set(request, server.requestIP(request))
|
|
434
467
|
const answered = await fetch(request)
|
|
435
468
|
return admitsMux(request, answered, booted)
|
|
436
469
|
? upgraded(server, request, {
|
|
@@ -465,6 +498,26 @@ export async function bootEntered(
|
|
|
465
498
|
}
|
|
466
499
|
}
|
|
467
500
|
|
|
501
|
+
/**
|
|
502
|
+
* What a root rung is handed: the request, and what answers it, asked on the first read.
|
|
503
|
+
*
|
|
504
|
+
* 37.19 — LAZILY, because routing a request twice is a cost only an app whose root rungs read the
|
|
505
|
+
* lane pays: the dispatch routes it again after them, and an app with none reads nothing here.
|
|
506
|
+
*/
|
|
507
|
+
class RootContext implements AppContext {
|
|
508
|
+
readonly request: Request
|
|
509
|
+
#sources: AppSources
|
|
510
|
+
#lane: Lane | undefined = undefined
|
|
511
|
+
constructor(sources: AppSources, request: Request) {
|
|
512
|
+
this.#sources = sources
|
|
513
|
+
this.request = request
|
|
514
|
+
}
|
|
515
|
+
get lane(): Lane {
|
|
516
|
+
this.#lane ??= laneOf(this.#sources, this.request)
|
|
517
|
+
return this.#lane
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
468
521
|
/**
|
|
469
522
|
* The answer with the headers every answer carries, and what was written through `response`.
|
|
470
523
|
*
|
package/src/server/devWorker.ts
CHANGED
|
@@ -35,7 +35,7 @@ import { bootApp, type BootedApp } from './createApp.ts'
|
|
|
35
35
|
import { keepServing } from './faultPolicy.ts'
|
|
36
36
|
import { type Manifest, NO_MANIFEST } from './manifestOf.ts'
|
|
37
37
|
import { admitsMux, muxSocketOptions } from './muxUpgrade.ts'
|
|
38
|
-
import { LISTENING } from './requestScope.ts'
|
|
38
|
+
import { LISTENING, RECEIVED_FROM, type ReceivedFrom } from './requestScope.ts'
|
|
39
39
|
import {
|
|
40
40
|
describe,
|
|
41
41
|
type FromRelay,
|
|
@@ -70,6 +70,7 @@ export type ToWorker =
|
|
|
70
70
|
connection: number
|
|
71
71
|
url: string
|
|
72
72
|
headers: [string, string][]
|
|
73
|
+
from: ReceivedFrom
|
|
73
74
|
}
|
|
74
75
|
| { kind: 'socket-message'; connection: number; text: string }
|
|
75
76
|
| { kind: 'socket-close'; connection: number }
|
|
@@ -113,7 +114,9 @@ let failure = ''
|
|
|
113
114
|
const relay = serveOver(
|
|
114
115
|
{ postMessage: toListener },
|
|
115
116
|
{
|
|
116
|
-
async fetch(request) {
|
|
117
|
+
async fetch(request, from) {
|
|
118
|
+
// 22.12 — where the listener received it from, which is not this process.
|
|
119
|
+
RECEIVED_FROM.set(request, from)
|
|
117
120
|
await opening
|
|
118
121
|
return app === undefined
|
|
119
122
|
? failedDocument(failure)
|
|
@@ -161,21 +164,20 @@ async function receive(message: ToWorker): Promise<void> {
|
|
|
161
164
|
await opening
|
|
162
165
|
if (message.kind === 'socket-open') {
|
|
163
166
|
const numbered = message.connection
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
},
|
|
174
|
-
// NOTHING TO CLOSE: the listener holds the browser's socket, and it outlives this
|
|
175
|
-
// worker (38.14). Nothing here stops the mux, either.
|
|
176
|
-
close() {},
|
|
167
|
+
const upgrade = new Request(message.url, { headers: message.headers })
|
|
168
|
+
RECEIVED_FROM.set(upgrade, message.from)
|
|
169
|
+
const connection = mux?.connect(upgrade, {
|
|
170
|
+
send(text: string) {
|
|
171
|
+
toListener({
|
|
172
|
+
kind: 'socket-send',
|
|
173
|
+
connection: numbered,
|
|
174
|
+
text,
|
|
175
|
+
})
|
|
177
176
|
},
|
|
178
|
-
|
|
177
|
+
// NOTHING TO CLOSE: the listener holds the browser's socket, and it outlives this
|
|
178
|
+
// worker (38.14). Nothing here stops the mux, either.
|
|
179
|
+
close() {},
|
|
180
|
+
})
|
|
179
181
|
if (connection !== undefined) connections.set(numbered, connection)
|
|
180
182
|
return
|
|
181
183
|
}
|
package/src/server/faceOf.ts
CHANGED
|
@@ -43,10 +43,12 @@ export type Asked =
|
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* One handler file, as `Compilation.handlers` holds it — and 19.9's schema its default's annotation
|
|
46
|
-
* derives, which the module at the file's path holds it to (`derivedArgs`)
|
|
46
|
+
* derives, which the module at the file's path holds it to (`derivedArgs`), and the address it is
|
|
47
|
+
* stamped with on load (16.2).
|
|
47
48
|
*/
|
|
48
49
|
export type HandlerFace = {
|
|
49
50
|
scope: string
|
|
51
|
+
address: string
|
|
50
52
|
client: string
|
|
51
53
|
args: JsonValue | undefined
|
|
52
54
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { isFailed } from '#shared/Failed.ts'
|
|
17
17
|
import { essenceOf } from '#shared/framing.ts'
|
|
18
|
+
import { IMAGE_TYPES } from '#shared/IMAGE_TYPES.ts'
|
|
18
19
|
import { namedLogger } from '#shared/log.ts'
|
|
19
20
|
import { mounted } from '#shared/mount.ts'
|
|
20
21
|
import { type Reactive, reactive } from '#shared/reactive/index.ts'
|
|
@@ -26,7 +27,6 @@ import { bodyWithin } from './answerRpc.ts'
|
|
|
26
27
|
import type { ToolAnswer, ToolDefinition } from './assistant.ts'
|
|
27
28
|
import { appIdentity } from './config.ts'
|
|
28
29
|
import { crossingOrigin, crossingRefusal } from './crossingOrigin.ts'
|
|
29
|
-
import { IMAGE_TYPES } from './IMAGE_TYPES.ts'
|
|
30
30
|
import { MCP_PROTOCOL_VERSION } from './MCP_PROTOCOL_VERSION.ts'
|
|
31
31
|
import type { Mounted } from './mountHandlers.ts'
|
|
32
32
|
import { type Listed, surfaceOf } from './surfaceOf.ts'
|
|
@@ -326,7 +326,7 @@ export class McpSurface {
|
|
|
326
326
|
|
|
327
327
|
/** What calling the tool `name` as `asker` answered, a refusal or a missing tool a failed one. */
|
|
328
328
|
call(name: string, args: unknown, asker: Request): Promise<ToolAnswer> {
|
|
329
|
-
const tool = this
|
|
329
|
+
const tool = this.tool(name)
|
|
330
330
|
if (tool === undefined) {
|
|
331
331
|
logCall(String(name), 404, undefined)
|
|
332
332
|
return Promise.resolve({
|
|
@@ -337,12 +337,13 @@ export class McpSurface {
|
|
|
337
337
|
return this.#called(tool, args as Record<string, unknown>, asker)
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
|
|
340
|
+
/** The tool it lists as `name`, or `undefined` where it lists none. */
|
|
341
|
+
tool(name: unknown): Listed | undefined {
|
|
341
342
|
return this.#published().find((one) => one.name === name)
|
|
342
343
|
}
|
|
343
344
|
|
|
344
345
|
async #called(
|
|
345
|
-
one:
|
|
346
|
+
one: Listed,
|
|
346
347
|
args: Record<string, unknown> | undefined,
|
|
347
348
|
asker: Request,
|
|
348
349
|
): Promise<ToolAnswer> {
|
|
@@ -416,7 +417,7 @@ export class McpSurface {
|
|
|
416
417
|
case 'tools/list':
|
|
417
418
|
return { result: { tools: this.list() } }
|
|
418
419
|
case 'tools/call': {
|
|
419
|
-
const tool = this
|
|
420
|
+
const tool = this.tool(params?.name)
|
|
420
421
|
if (tool === undefined) {
|
|
421
422
|
logCall(String(params?.name), 404, undefined)
|
|
422
423
|
return mcpFailure(
|
|
@@ -13,6 +13,7 @@ export function handlerFileMount(
|
|
|
13
13
|
| 'cli'
|
|
14
14
|
| 'mcp'
|
|
15
15
|
| 'description'
|
|
16
|
+
| 'middleware'
|
|
16
17
|
| 'copy'
|
|
17
18
|
| 'defaults'
|
|
18
19
|
| 'args'
|
|
@@ -32,6 +33,7 @@ export function handlerFileMount(
|
|
|
32
33
|
cli: declared.cli,
|
|
33
34
|
mcp: declared.mcp,
|
|
34
35
|
description: declared.description,
|
|
36
|
+
middleware: declared.middleware,
|
|
35
37
|
copy: declared.copy,
|
|
36
38
|
defaults: declared.defaults,
|
|
37
39
|
args: declared.args,
|
package/src/server/hooks.ts
CHANGED
|
@@ -26,11 +26,29 @@ import { config } from './config.ts'
|
|
|
26
26
|
import type { LauncherOptions } from './launcherOptions.ts'
|
|
27
27
|
import { currentRequestScope } from './requestScope.ts'
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* What answers a request, by the lane the app sends it down. 37.19.
|
|
31
|
+
*
|
|
32
|
+
* One per arm of `answerRequest`'s dispatch: a page (and every address no route answers), a
|
|
33
|
+
* handler, a public file, the three surfaces of abide's own a gate decides about one by one, the
|
|
34
|
+
* socket mux's upgrade, and the rest of `/__abide`.
|
|
35
|
+
*/
|
|
36
|
+
export type Lane =
|
|
37
|
+
| 'page'
|
|
38
|
+
| 'handler'
|
|
39
|
+
| 'file'
|
|
40
|
+
| 'openapi'
|
|
41
|
+
| 'mcp'
|
|
42
|
+
| 'bridge'
|
|
43
|
+
| 'socket'
|
|
44
|
+
| 'abide'
|
|
45
|
+
|
|
46
|
+
/** What an app rung is handed: the request, and what answers it (37.19). */
|
|
47
|
+
export type AppContext = { request: Request; lane: Lane }
|
|
30
48
|
|
|
31
49
|
/** A rung `middleware` files: what `next` answered passed on, or a refusal in its place (37.14). */
|
|
32
50
|
export type AppRung = (
|
|
33
|
-
next: (ctx?:
|
|
51
|
+
next: (ctx?: { request: Request }) => Promise<Response>,
|
|
34
52
|
ctx: AppContext,
|
|
35
53
|
) =>
|
|
36
54
|
| Response
|
|
@@ -130,18 +148,46 @@ export function inOrder<T>(list: readonly Filed<T>[]): T[] {
|
|
|
130
148
|
* 37.14, 37.15 — PER LINK, not at the onion's edge: a rung outside this one is handed the answer
|
|
131
149
|
* from `next`, so one that reads a `Response` (29.2's) reads one whichever rung inside it refused.
|
|
132
150
|
*/
|
|
133
|
-
function answering(
|
|
151
|
+
function answering(
|
|
152
|
+
rung: AppRung,
|
|
153
|
+
answer: RefusalAnswer,
|
|
154
|
+
laneOf: ((request: Request) => Lane) | undefined,
|
|
155
|
+
): LaneRung {
|
|
134
156
|
return async (next, ctx) => {
|
|
135
|
-
|
|
157
|
+
// 37.19 — A REQUEST A RUNG HANDS `next` IS ANSWERED BY WHAT ITS OWN ADDRESS ROUTES TO, asked
|
|
158
|
+
// again before routing (`laneOf`) and kept after it, where routing has already chosen.
|
|
159
|
+
const result = await rung(
|
|
160
|
+
(given) =>
|
|
161
|
+
next(
|
|
162
|
+
given === undefined || given.request === ctx.request
|
|
163
|
+
? ctx
|
|
164
|
+
: {
|
|
165
|
+
request: given.request,
|
|
166
|
+
lane:
|
|
167
|
+
laneOf === undefined
|
|
168
|
+
? ctx.lane
|
|
169
|
+
: laneOf(given.request),
|
|
170
|
+
},
|
|
171
|
+
),
|
|
172
|
+
ctx,
|
|
173
|
+
)
|
|
136
174
|
return isFailed(result) ? answer(result, ctx.request) : result
|
|
137
175
|
}
|
|
138
176
|
}
|
|
139
177
|
|
|
140
|
-
/**
|
|
141
|
-
|
|
178
|
+
/**
|
|
179
|
+
* The rungs filed at the root route, which run before routing. 37.3.
|
|
180
|
+
*
|
|
181
|
+
* `laneOf` is what the dispatch would answer a request with (37.19), asked of one a rung hands on.
|
|
182
|
+
*/
|
|
183
|
+
export function rootRungs(
|
|
184
|
+
answer: RefusalAnswer,
|
|
185
|
+
laneOf: (request: Request) => Lane,
|
|
186
|
+
): LaneRung[] {
|
|
142
187
|
const rungs: LaneRung[] = []
|
|
143
188
|
for (const entry of HOOKS.rungs)
|
|
144
|
-
if (entry.route === ROOT)
|
|
189
|
+
if (entry.route === ROOT)
|
|
190
|
+
rungs.push(answering(entry.item, answer, laneOf))
|
|
145
191
|
return rungs
|
|
146
192
|
}
|
|
147
193
|
|
|
@@ -156,7 +202,7 @@ export function rungsOver(route: string, answer: RefusalAnswer): LaneRung[] {
|
|
|
156
202
|
const rungs: LaneRung[] = []
|
|
157
203
|
for (const entry of HOOKS.rungs)
|
|
158
204
|
if (entry.route !== ROOT && coversRoute(entry.segments, segments))
|
|
159
|
-
rungs.push(answering(entry.item, answer))
|
|
205
|
+
rungs.push(answering(entry.item, answer, undefined))
|
|
160
206
|
return rungs
|
|
161
207
|
}
|
|
162
208
|
|
package/src/server/index.ts
CHANGED
|
@@ -26,13 +26,17 @@ export {
|
|
|
26
26
|
type RpcOptions,
|
|
27
27
|
} from '#shared/rpc.ts'
|
|
28
28
|
// 45 — a conversation with a model, piped through a handler, and the two providers abide ships.
|
|
29
|
+
// 44 — what a page sends and is handed back, which the isomorphic entry carries too.
|
|
30
|
+
export type {
|
|
31
|
+
AssistantContent,
|
|
32
|
+
AssistantEvent,
|
|
33
|
+
AssistantMessage,
|
|
34
|
+
} from '#shared/AssistantEvent.ts'
|
|
29
35
|
export {
|
|
30
|
-
type AssistantContent,
|
|
31
|
-
type AssistantEvent,
|
|
32
|
-
type AssistantMessage,
|
|
33
36
|
type AssistantOptions,
|
|
34
37
|
assistant,
|
|
35
38
|
type Provider,
|
|
39
|
+
type ProviderEvent,
|
|
36
40
|
type ToolAnswer,
|
|
37
41
|
type Toolbox,
|
|
38
42
|
type ToolDefinition,
|
|
@@ -52,6 +56,9 @@ export { type App, createApp } from './createApp.ts'
|
|
|
52
56
|
// 41.28's run-time half: an emitted component reads it on either side of its setup, and one whose
|
|
53
57
|
// setup made a call answers as a page does. Not an app's name.
|
|
54
58
|
export { callsInRender } from './PageRender.ts'
|
|
59
|
+
// 16.88's run-time half: what the module a handler file is loaded through calls to give it its
|
|
60
|
+
// address. Not an app's name.
|
|
61
|
+
export { addressed } from './addressed.ts'
|
|
55
62
|
// 19.9's run-time half: what the module a handler is loaded through calls to hold it to the schema
|
|
56
63
|
// its annotation derives (15.8). Not an app's name.
|
|
57
64
|
export { derivedArgs } from './argsContract.ts'
|
|
@@ -62,6 +69,7 @@ export {
|
|
|
62
69
|
type ConfigOptions,
|
|
63
70
|
type Env,
|
|
64
71
|
fileRoute,
|
|
72
|
+
type Lane,
|
|
65
73
|
middleware,
|
|
66
74
|
onConfig,
|
|
67
75
|
onError,
|
|
@@ -80,7 +88,14 @@ export { type Component, render } from './render.ts'
|
|
|
80
88
|
// child's markup one thing for the client to walk to.
|
|
81
89
|
export { renderComponent } from './renderComponent.ts'
|
|
82
90
|
// 22.1 — the request being served, the response being built for it, and the listening server.
|
|
83
|
-
|
|
91
|
+
// 22.12 — and where it came from.
|
|
92
|
+
export {
|
|
93
|
+
type ReceivedFrom,
|
|
94
|
+
request,
|
|
95
|
+
requestIP,
|
|
96
|
+
response,
|
|
97
|
+
server,
|
|
98
|
+
} from './requestScope.ts'
|
|
84
99
|
// 17 — the response helpers, each a write through `response`.
|
|
85
100
|
export {
|
|
86
101
|
bytes,
|
|
@@ -92,6 +107,8 @@ export {
|
|
|
92
107
|
sse,
|
|
93
108
|
type Values,
|
|
94
109
|
} from './responseHelpers.ts'
|
|
110
|
+
// 8.25 — a `Store` in a `bun:sqlite` table.
|
|
111
|
+
export { sqliteStore } from './sqliteStore.ts'
|
|
95
112
|
// 24 — the trace this work belongs to.
|
|
96
113
|
export { trace } from './trace.ts'
|
|
97
114
|
|
|
@@ -222,7 +222,7 @@ async function routeAdmits(
|
|
|
222
222
|
const answered = await compose(mounted.rungs, async () => {
|
|
223
223
|
reached = true
|
|
224
224
|
return new Response(null, { status: 204 })
|
|
225
|
-
})({ request })
|
|
225
|
+
})({ request, lane: 'handler' })
|
|
226
226
|
return reached && answered.ok
|
|
227
227
|
} catch {
|
|
228
228
|
return false
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
import type { CopyOptions } from '#shared/CopyOptions.ts'
|
|
5
5
|
import type { JsonValue } from '#shared/JsonValue.ts'
|
|
6
|
-
import { mounted as underMount } from '#shared/mount.ts'
|
|
7
6
|
import type { Gated, HeldToAnnotation } from '#shared/reactive/argsContract.ts'
|
|
8
7
|
import type { Defaults } from '#shared/reactive/parameterDefaults.ts'
|
|
9
8
|
import type { Method, Rpc } from '#shared/rpc.ts'
|
|
10
9
|
|
|
10
|
+
import { addressed } from './addressed.ts'
|
|
11
11
|
import { answered } from './answerFailure.ts'
|
|
12
12
|
import { derivedArgs } from './argsContract.ts'
|
|
13
13
|
import { type LaneRung, rungsOver } from './hooks.ts'
|
|
@@ -44,6 +44,11 @@ export type Mount = {
|
|
|
44
44
|
mcp: boolean
|
|
45
45
|
/** Its `description`, where the declaration writes one as a literal the build can read. */
|
|
46
46
|
description: string | undefined
|
|
47
|
+
/**
|
|
48
|
+
* The rung each entry of its declaration's `middleware` names, as written; `null` where the
|
|
49
|
+
* options are not written where the build can read them. 38.135.
|
|
50
|
+
*/
|
|
51
|
+
middleware: string[] | null
|
|
47
52
|
/**
|
|
48
53
|
* What a `GET`'s value declares that the browser's copy behaves by, or `undefined`.
|
|
49
54
|
*
|
|
@@ -111,11 +116,7 @@ export function mountedOwn(
|
|
|
111
116
|
name: string,
|
|
112
117
|
handler: object,
|
|
113
118
|
): [string, Mounted] {
|
|
114
|
-
|
|
115
|
-
// the configuration's, and nothing boots this handler.
|
|
116
|
-
Object.defineProperty(handler, 'address', {
|
|
117
|
-
get: () => underMount(address),
|
|
118
|
-
})
|
|
119
|
+
addressed(handler, address)
|
|
119
120
|
return [
|
|
120
121
|
address,
|
|
121
122
|
{
|
|
@@ -134,6 +135,7 @@ export function mountedOwn(
|
|
|
134
135
|
cli: false,
|
|
135
136
|
mcp: false,
|
|
136
137
|
description: undefined,
|
|
138
|
+
middleware: [],
|
|
137
139
|
copy: undefined,
|
|
138
140
|
defaults: undefined,
|
|
139
141
|
args: undefined,
|
|
@@ -162,7 +164,7 @@ export async function mountHandlers(
|
|
|
162
164
|
const handler = exported as Mounted['handler']
|
|
163
165
|
// 16.9, 16.10 — the address `rpc.url` and `rpc.raw` build on, under the mount — and what a
|
|
164
166
|
// page's render turns a call it made into the browser's request by (41.20).
|
|
165
|
-
handler
|
|
167
|
+
addressed(handler, mount.address)
|
|
166
168
|
// 19.9 — the schema the build derived off its annotation, for a handler whose module did not
|
|
167
169
|
// file it on load: a bundled executable's faces carry no `derivedArgs` call.
|
|
168
170
|
if (mount.args !== undefined) derivedArgs(handler, mount.args)
|