@byline/host-tanstack-start 1.8.1 → 1.9.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/dist/integrations/byline-ai.d.ts +34 -0
- package/dist/integrations/byline-ai.js +33 -0
- package/dist/routes/create-admin-layout-route.js +19 -16
- package/dist/server-fns/ai/execute.d.ts +19 -0
- package/dist/server-fns/ai/execute.js +49 -0
- package/dist/server-fns/ai/index.d.ts +15 -0
- package/dist/server-fns/ai/index.js +1 -0
- package/package.json +11 -6
- package/src/integrations/byline-ai.tsx +82 -0
- package/src/integrations/serve-uploads.ts +2 -1
- package/src/routes/create-admin-layout-route.tsx +13 -10
- package/src/server-fns/ai/execute.ts +79 -0
- package/src/server-fns/ai/index.ts +17 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Bridges the `@byline/ai` plugins to TanStack Start's RPC transport.
|
|
10
|
+
*
|
|
11
|
+
* The plugins make `fetch(endpoint, init)` calls. We substitute a
|
|
12
|
+
* fetch-shaped adapter that invokes `executeAiInstruction` (a server
|
|
13
|
+
* function with admin-auth enforcement) and returns its `Response` —
|
|
14
|
+
* preserving the NDJSON streaming wire format the plugins already
|
|
15
|
+
* understand.
|
|
16
|
+
*
|
|
17
|
+
* Hosts mount `<BylineAiAdminProvider>` once inside the admin shell.
|
|
18
|
+
*/
|
|
19
|
+
import type { ReactNode } from 'react';
|
|
20
|
+
/**
|
|
21
|
+
* `fetch`-shaped adapter that dispatches the body to the
|
|
22
|
+
* `executeAiInstruction` server function. The `input` URL is ignored —
|
|
23
|
+
* the server function handles routing and auth.
|
|
24
|
+
*/
|
|
25
|
+
export declare const aiFetchAdapter: typeof fetch;
|
|
26
|
+
/**
|
|
27
|
+
* Single-line provider mount for the admin shell. Wires `@byline/ai`'s
|
|
28
|
+
* public config to the host-side server function. Set `enabled={false}`
|
|
29
|
+
* to allow wrapper fields to hide AI affordances globally.
|
|
30
|
+
*/
|
|
31
|
+
export declare function BylineAiAdminProvider({ children, enabled, }: {
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { AiPublicConfigProvider } from "@byline/ai";
|
|
3
|
+
import { executeAiInstruction } from "../server-fns/ai/index.js";
|
|
4
|
+
const aiFetchAdapter = async (_input, init)=>{
|
|
5
|
+
const bodyText = 'string' == typeof init?.body ? init.body : init?.body == null ? '' : await new Response(init.body).text();
|
|
6
|
+
let payload;
|
|
7
|
+
try {
|
|
8
|
+
payload = JSON.parse(bodyText);
|
|
9
|
+
} catch {
|
|
10
|
+
return new Response(JSON.stringify({
|
|
11
|
+
error: 'Invalid JSON payload'
|
|
12
|
+
}), {
|
|
13
|
+
status: 400,
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json'
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return executeAiInstruction({
|
|
20
|
+
data: payload,
|
|
21
|
+
signal: init?.signal ?? void 0
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
function BylineAiAdminProvider({ children, enabled }) {
|
|
25
|
+
return /*#__PURE__*/ jsx(AiPublicConfigProvider, {
|
|
26
|
+
config: {
|
|
27
|
+
fetch: aiFetchAdapter,
|
|
28
|
+
enabled
|
|
29
|
+
},
|
|
30
|
+
children: children
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export { BylineAiAdminProvider, aiFetchAdapter };
|
|
@@ -10,6 +10,7 @@ import { AdminMenuDrawer } from "../admin-shell/chrome/menu-drawer.js";
|
|
|
10
10
|
import { AdminMenuProvider } from "../admin-shell/chrome/menu-provider.js";
|
|
11
11
|
import { RouteError, RouteNotFound } from "../admin-shell/chrome/route-error.js";
|
|
12
12
|
import { bylineAdminServices } from "../integrations/byline-admin-services.js";
|
|
13
|
+
import { BylineAiAdminProvider } from "../integrations/byline-ai.js";
|
|
13
14
|
import { bylineFieldServices } from "../integrations/byline-field-services.js";
|
|
14
15
|
import { getCurrentAdminUser } from "../server-fns/auth/index.js";
|
|
15
16
|
function createAdminLayoutRoute(path, opts = {}) {
|
|
@@ -36,22 +37,24 @@ function createAdminLayoutRoute(path, opts = {}) {
|
|
|
36
37
|
services: bylineAdminServices,
|
|
37
38
|
children: /*#__PURE__*/ jsx(BylineFieldServicesProvider, {
|
|
38
39
|
services: bylineFieldServices,
|
|
39
|
-
children: /*#__PURE__*/
|
|
40
|
-
children:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
40
|
+
children: /*#__PURE__*/ jsx(BylineAiAdminProvider, {
|
|
41
|
+
children: /*#__PURE__*/ jsxs(AdminMenuProvider, {
|
|
42
|
+
children: [
|
|
43
|
+
/*#__PURE__*/ jsx(AdminAppBar, {
|
|
44
|
+
user: user
|
|
45
|
+
}),
|
|
46
|
+
/*#__PURE__*/ jsxs("main", {
|
|
47
|
+
className: classnames('byline-admin-layout-main', admin_layout_module.main),
|
|
48
|
+
children: [
|
|
49
|
+
/*#__PURE__*/ jsx(DrawerToggle, {}),
|
|
50
|
+
/*#__PURE__*/ jsx(AdminMenuDrawer, {}),
|
|
51
|
+
/*#__PURE__*/ jsx(Content, {
|
|
52
|
+
children: /*#__PURE__*/ jsx(Outlet, {})
|
|
53
|
+
})
|
|
54
|
+
]
|
|
55
|
+
})
|
|
56
|
+
]
|
|
57
|
+
})
|
|
55
58
|
})
|
|
56
59
|
})
|
|
57
60
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { ExecuteInstructionOptions, ExecuteInstructionParams } from '@byline/ai';
|
|
9
|
+
/**
|
|
10
|
+
* Wire shape — the same as `ExecuteInstruction` minus the in-process-only
|
|
11
|
+
* `signal` (an AbortSignal can't be serialized across the RPC boundary;
|
|
12
|
+
* the client passes its signal separately as a fetcher option).
|
|
13
|
+
*/
|
|
14
|
+
type ExecuteAiInstructionInput = {
|
|
15
|
+
params: ExecuteInstructionParams;
|
|
16
|
+
options?: Omit<ExecuteInstructionOptions, 'signal'>;
|
|
17
|
+
};
|
|
18
|
+
export declare const executeAiInstruction: import("@tanstack/react-start").RequiredFetcher<undefined, (input: ExecuteAiInstructionInput) => ExecuteAiInstructionInput, Promise<Response>>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
2
|
+
import { executeInstruction, executeInstructionStreaming } from "@byline/ai/server";
|
|
3
|
+
import { getAdminRequestContext } from "../../auth/auth-context.js";
|
|
4
|
+
const executeAiInstruction = createServerFn({
|
|
5
|
+
method: 'POST'
|
|
6
|
+
}).inputValidator((input)=>input).handler(async ({ data })=>{
|
|
7
|
+
await getAdminRequestContext();
|
|
8
|
+
if (data.options?.streaming) {
|
|
9
|
+
const streamResult = executeInstructionStreaming(data.params, data.options);
|
|
10
|
+
const ndjson = new ReadableStream({
|
|
11
|
+
async start (controller) {
|
|
12
|
+
const encoder = new TextEncoder();
|
|
13
|
+
const enqueue = (obj)=>{
|
|
14
|
+
controller.enqueue(encoder.encode(`${JSON.stringify(obj)}\n`));
|
|
15
|
+
};
|
|
16
|
+
try {
|
|
17
|
+
for await (const chunk of streamResult.text)enqueue({
|
|
18
|
+
type: 'delta',
|
|
19
|
+
text: chunk
|
|
20
|
+
});
|
|
21
|
+
const finalState = await streamResult.final;
|
|
22
|
+
enqueue({
|
|
23
|
+
type: 'final',
|
|
24
|
+
state: finalState
|
|
25
|
+
});
|
|
26
|
+
} catch (err) {
|
|
27
|
+
enqueue({
|
|
28
|
+
type: 'error',
|
|
29
|
+
message: err instanceof Error ? err.message : 'AI stream error'
|
|
30
|
+
});
|
|
31
|
+
} finally{
|
|
32
|
+
controller.close();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return new Response(ndjson, {
|
|
37
|
+
headers: {
|
|
38
|
+
'Content-Type': 'application/x-ndjson; charset=utf-8'
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const state = await executeInstruction(data.params, data.options);
|
|
43
|
+
return new Response(JSON.stringify(state), {
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': 'application/json; charset=utf-8'
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
export { executeAiInstruction };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Admin-only AI server functions backing the `@byline/ai` plugins.
|
|
10
|
+
*
|
|
11
|
+
* The plugins use a `fetch`-shaped API; the host adapter ships a
|
|
12
|
+
* `aiFetchAdapter` in `@byline/host-tanstack-start/integrations/byline-ai`
|
|
13
|
+
* that wraps `executeAiInstruction` so the plugins remain transport-agnostic.
|
|
14
|
+
*/
|
|
15
|
+
export { executeAiInstruction } from './execute';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { executeAiInstruction } from "./execute.js";
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.9.0",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.9.0"
|
|
9
9
|
},
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
"types": "./dist/server-fns/admin-account/index.d.ts",
|
|
46
46
|
"import": "./dist/server-fns/admin-account/index.js"
|
|
47
47
|
},
|
|
48
|
+
"./server-fns/ai": {
|
|
49
|
+
"types": "./dist/server-fns/ai/index.d.ts",
|
|
50
|
+
"import": "./dist/server-fns/ai/index.js"
|
|
51
|
+
},
|
|
48
52
|
"./server-fns/admin-permissions": {
|
|
49
53
|
"types": "./dist/server-fns/admin-permissions/index.d.ts",
|
|
50
54
|
"import": "./dist/server-fns/admin-permissions/index.js"
|
|
@@ -101,11 +105,12 @@
|
|
|
101
105
|
"react-swipeable": "^7.0.2",
|
|
102
106
|
"uuid": "^14.0.0",
|
|
103
107
|
"zod": "^4.4.2",
|
|
104
|
-
"@byline/
|
|
105
|
-
"@byline/
|
|
106
|
-
"@byline/
|
|
107
|
-
"@byline/
|
|
108
|
-
"@byline/
|
|
108
|
+
"@byline/admin": "1.9.0",
|
|
109
|
+
"@byline/ai": "1.9.0",
|
|
110
|
+
"@byline/client": "1.9.0",
|
|
111
|
+
"@byline/core": "1.9.0",
|
|
112
|
+
"@byline/auth": "1.9.0",
|
|
113
|
+
"@byline/ui": "1.9.0"
|
|
109
114
|
},
|
|
110
115
|
"peerDependencies": {
|
|
111
116
|
"@tanstack/react-router": "^1.167.0",
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Bridges the `@byline/ai` plugins to TanStack Start's RPC transport.
|
|
11
|
+
*
|
|
12
|
+
* The plugins make `fetch(endpoint, init)` calls. We substitute a
|
|
13
|
+
* fetch-shaped adapter that invokes `executeAiInstruction` (a server
|
|
14
|
+
* function with admin-auth enforcement) and returns its `Response` —
|
|
15
|
+
* preserving the NDJSON streaming wire format the plugins already
|
|
16
|
+
* understand.
|
|
17
|
+
*
|
|
18
|
+
* Hosts mount `<BylineAiAdminProvider>` once inside the admin shell.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ReactNode } from 'react'
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
AiPublicConfigProvider,
|
|
25
|
+
type ExecuteInstructionOptions,
|
|
26
|
+
type ExecuteInstructionParams,
|
|
27
|
+
} from '@byline/ai'
|
|
28
|
+
|
|
29
|
+
import { executeAiInstruction } from '../server-fns/ai/index.js'
|
|
30
|
+
|
|
31
|
+
type ExecuteAiInstructionWire = {
|
|
32
|
+
params: ExecuteInstructionParams
|
|
33
|
+
options?: Omit<ExecuteInstructionOptions, 'signal'>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `fetch`-shaped adapter that dispatches the body to the
|
|
38
|
+
* `executeAiInstruction` server function. The `input` URL is ignored —
|
|
39
|
+
* the server function handles routing and auth.
|
|
40
|
+
*/
|
|
41
|
+
export const aiFetchAdapter: typeof fetch = async (_input, init) => {
|
|
42
|
+
const bodyText =
|
|
43
|
+
typeof init?.body === 'string'
|
|
44
|
+
? init.body
|
|
45
|
+
: init?.body == null
|
|
46
|
+
? ''
|
|
47
|
+
: await new Response(init.body as BodyInit).text()
|
|
48
|
+
|
|
49
|
+
let payload: ExecuteAiInstructionWire
|
|
50
|
+
try {
|
|
51
|
+
payload = JSON.parse(bodyText) as ExecuteAiInstructionWire
|
|
52
|
+
} catch {
|
|
53
|
+
return new Response(JSON.stringify({ error: 'Invalid JSON payload' }), {
|
|
54
|
+
status: 400,
|
|
55
|
+
headers: { 'Content-Type': 'application/json' },
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return executeAiInstruction({
|
|
60
|
+
data: payload,
|
|
61
|
+
signal: init?.signal ?? undefined,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Single-line provider mount for the admin shell. Wires `@byline/ai`'s
|
|
67
|
+
* public config to the host-side server function. Set `enabled={false}`
|
|
68
|
+
* to allow wrapper fields to hide AI affordances globally.
|
|
69
|
+
*/
|
|
70
|
+
export function BylineAiAdminProvider({
|
|
71
|
+
children,
|
|
72
|
+
enabled,
|
|
73
|
+
}: {
|
|
74
|
+
children: ReactNode
|
|
75
|
+
enabled?: boolean
|
|
76
|
+
}) {
|
|
77
|
+
return (
|
|
78
|
+
<AiPublicConfigProvider config={{ fetch: aiFetchAdapter, enabled }}>
|
|
79
|
+
{children}
|
|
80
|
+
</AiPublicConfigProvider>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* `createUploadsHandler({ dir, prefix })`.
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
+
import type { Stats } from 'node:fs'
|
|
45
46
|
import { readFile, stat } from 'node:fs/promises'
|
|
46
47
|
import { resolve as resolvePath } from 'node:path'
|
|
47
48
|
|
|
@@ -123,7 +124,7 @@ export function createUploadsHandler(
|
|
|
123
124
|
return new Response('Forbidden', { status: 403 })
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
let info
|
|
127
|
+
let info: Stats
|
|
127
128
|
try {
|
|
128
129
|
info = await stat(abs)
|
|
129
130
|
} catch {
|
|
@@ -32,6 +32,7 @@ import { AdminMenuDrawer } from '../admin-shell/chrome/menu-drawer.js'
|
|
|
32
32
|
import { AdminMenuProvider } from '../admin-shell/chrome/menu-provider.js'
|
|
33
33
|
import { RouteError, RouteNotFound } from '../admin-shell/chrome/route-error.js'
|
|
34
34
|
import { bylineAdminServices } from '../integrations/byline-admin-services.js'
|
|
35
|
+
import { BylineAiAdminProvider } from '../integrations/byline-ai.js'
|
|
35
36
|
import { bylineFieldServices } from '../integrations/byline-field-services.js'
|
|
36
37
|
import { getCurrentAdminUser } from '../server-fns/auth/index.js'
|
|
37
38
|
|
|
@@ -67,16 +68,18 @@ export function createAdminLayoutRoute(path: string, opts: AdminLayoutOpts = {})
|
|
|
67
68
|
return (
|
|
68
69
|
<BylineAdminServicesProvider services={bylineAdminServices}>
|
|
69
70
|
<BylineFieldServicesProvider services={bylineFieldServices}>
|
|
70
|
-
<
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
<BylineAiAdminProvider>
|
|
72
|
+
<AdminMenuProvider>
|
|
73
|
+
<AdminAppBar user={user} />
|
|
74
|
+
<main className={cx('byline-admin-layout-main', layoutStyles.main)}>
|
|
75
|
+
<DrawerToggle />
|
|
76
|
+
<AdminMenuDrawer />
|
|
77
|
+
<Content>
|
|
78
|
+
<Outlet />
|
|
79
|
+
</Content>
|
|
80
|
+
</main>
|
|
81
|
+
</AdminMenuProvider>
|
|
82
|
+
</BylineAiAdminProvider>
|
|
80
83
|
</BylineFieldServicesProvider>
|
|
81
84
|
</BylineAdminServicesProvider>
|
|
82
85
|
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Admin-only AI execute server function.
|
|
11
|
+
*
|
|
12
|
+
* Accepts an `ExecuteInstruction` payload, calls
|
|
13
|
+
* `executeInstruction` / `executeInstructionStreaming` from `@byline/ai`,
|
|
14
|
+
* and returns the result as a `Response` — JSON for non-streaming and
|
|
15
|
+
* NDJSON (`{ type: 'delta' | 'final' | 'error' }` lines) for streaming.
|
|
16
|
+
*
|
|
17
|
+
* Auth is enforced by `getAdminRequestContext()`, which throws
|
|
18
|
+
* `ERR_UNAUTHENTICATED` when no valid admin session is present.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
22
|
+
|
|
23
|
+
import type { ExecuteInstructionOptions, ExecuteInstructionParams } from '@byline/ai'
|
|
24
|
+
// Server-only execute API — kept on the `/server` subpath so the browser
|
|
25
|
+
// barrel for `@byline/ai` stays free of pino + the provider SDKs.
|
|
26
|
+
import { executeInstruction, executeInstructionStreaming } from '@byline/ai/server'
|
|
27
|
+
|
|
28
|
+
import { getAdminRequestContext } from '../../auth/auth-context.js'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Wire shape — the same as `ExecuteInstruction` minus the in-process-only
|
|
32
|
+
* `signal` (an AbortSignal can't be serialized across the RPC boundary;
|
|
33
|
+
* the client passes its signal separately as a fetcher option).
|
|
34
|
+
*/
|
|
35
|
+
type ExecuteAiInstructionInput = {
|
|
36
|
+
params: ExecuteInstructionParams
|
|
37
|
+
options?: Omit<ExecuteInstructionOptions, 'signal'>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const executeAiInstruction = createServerFn({ method: 'POST' })
|
|
41
|
+
.inputValidator((input: ExecuteAiInstructionInput) => input)
|
|
42
|
+
.handler(async ({ data }): Promise<Response> => {
|
|
43
|
+
// Throws ERR_UNAUTHENTICATED if there is no admin session.
|
|
44
|
+
await getAdminRequestContext()
|
|
45
|
+
|
|
46
|
+
if (data.options?.streaming) {
|
|
47
|
+
const streamResult = executeInstructionStreaming(data.params, data.options)
|
|
48
|
+
const ndjson = new ReadableStream<Uint8Array>({
|
|
49
|
+
async start(controller) {
|
|
50
|
+
const encoder = new TextEncoder()
|
|
51
|
+
const enqueue = (obj: unknown) => {
|
|
52
|
+
controller.enqueue(encoder.encode(`${JSON.stringify(obj)}\n`))
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
for await (const chunk of streamResult.text) {
|
|
56
|
+
enqueue({ type: 'delta', text: chunk })
|
|
57
|
+
}
|
|
58
|
+
const finalState = await streamResult.final
|
|
59
|
+
enqueue({ type: 'final', state: finalState })
|
|
60
|
+
} catch (err) {
|
|
61
|
+
enqueue({
|
|
62
|
+
type: 'error',
|
|
63
|
+
message: err instanceof Error ? err.message : 'AI stream error',
|
|
64
|
+
})
|
|
65
|
+
} finally {
|
|
66
|
+
controller.close()
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
return new Response(ndjson, {
|
|
71
|
+
headers: { 'Content-Type': 'application/x-ndjson; charset=utf-8' },
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const state = await executeInstruction(data.params, data.options)
|
|
76
|
+
return new Response(JSON.stringify(state), {
|
|
77
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
78
|
+
})
|
|
79
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Admin-only AI server functions backing the `@byline/ai` plugins.
|
|
11
|
+
*
|
|
12
|
+
* The plugins use a `fetch`-shaped API; the host adapter ships a
|
|
13
|
+
* `aiFetchAdapter` in `@byline/host-tanstack-start/integrations/byline-ai`
|
|
14
|
+
* that wraps `executeAiInstruction` so the plugins remain transport-agnostic.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export { executeAiInstruction } from './execute'
|