@byline/ai 3.0.2 → 3.1.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/README.md CHANGED
@@ -1,4 +1,70 @@
1
- # Infonomic AI
1
+ # @byline/ai
2
2
 
3
- Library components for AI
3
+ The AI subsystem for [Byline CMS](https://github.com/Byline-CMS/bylinecms.dev)
4
+ — provider-agnostic text generation and structured editing for the admin
5
+ editor, with built-in editor plugins for the text and Lexical richtext fields.
4
6
 
7
+ This package is part of Byline CMS — a developer-friendly, open-source
8
+ headless CMS with versioning, editorial workflow, and content translation as
9
+ first-class concerns.
10
+
11
+ ## What it provides
12
+
13
+ - **Provider-agnostic execution** — a single `ExecuteInstruction` contract over
14
+ OpenAI, Google (Gemini), and Anthropic, with non-streaming and streaming
15
+ variants (`executeInstruction` / `executeInstructionStreaming`).
16
+ - **Structured generation** — `generateStructured` / `generateStructuredStreaming`
17
+ for schema-constrained output.
18
+ - **Patch generation** — `patch` / `patchStreaming`, which edit an ordered array
19
+ of Lexical text nodes in place (used by the richtext field's AI drawer).
20
+ - **Editor plugins** — drop-in plugins for the two editor surfaces:
21
+ `@byline/ai/plugins/text` (`AiPluginText`) and `@byline/ai/plugins/lexical`
22
+ (`AiPluginLexical`, `AiLexicalExtension`, `TOGGLE_AI_DRAWER_COMMAND`).
23
+ - **Browser-safe config** — a public config provider and provider/model helpers
24
+ that carry no server SDK dependencies.
25
+
26
+ ## Browser / server split
27
+
28
+ The root entry is browser-safe; the SDK-backed execution code lives behind a
29
+ separate server entry. Import from the right one:
30
+
31
+ | Entry | Use from | Surface |
32
+ |---|---|---|
33
+ | `@byline/ai` | Browser **and** server | Types, `AiPublicConfigProvider` / `useAiPublicConfig`, provider/model helpers (`PROVIDERS`, `DEFAULT_MODELS`, `getDefaultModel`, …), `INSTRUCTION_MODES` |
34
+ | `@byline/ai/server` | Server only | `executeInstruction(Streaming)`, `generateStructured(Streaming)`, `patch(Streaming)`, `getAiServerConfig` |
35
+ | `@byline/ai/plugins/text` | Browser | `AiPluginText` |
36
+ | `@byline/ai/plugins/lexical` | Browser | `AiPluginLexical`, `AiLexicalExtension`, `TOGGLE_AI_DRAWER_COMMAND` |
37
+
38
+ Importing `@byline/ai/server` in the browser will crash — it pulls in the
39
+ Anthropic/OpenAI/Google SDKs and pino. Host adapters mount the execute endpoint
40
+ on the server and the plugins POST `ExecuteInstruction` payloads to it; see the
41
+ host integration in `@byline/host-tanstack-start` (`integrations/byline-ai.tsx`,
42
+ `server-fns/ai/*`).
43
+
44
+ ## Configuration
45
+
46
+ Server config is read from the environment by `getAiServerConfig()`:
47
+
48
+ | Variable | Purpose | Default |
49
+ |---|---|---|
50
+ | `AI_DEFAULT_PROVIDER` | `openai` \| `google` \| `anthropic` | `openai` |
51
+ | `OPENAI_API_KEY` / `OPENAI_BASE_URL` | OpenAI credentials | — |
52
+ | `GOOGLE_API_KEY` / `GOOGLE_BASE_URL` | Google (Gemini) credentials | — |
53
+ | `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL` | Anthropic credentials | — |
54
+ | `BYLINE_AI_ENDPOINT` | Endpoint the editor plugins POST to | `/admin/api/ai/execute` |
55
+ | `LOG_LEVEL` / `LOG_PRETTY` | Logging | `info` |
56
+
57
+ The curated per-provider model lists live in `src/config/ai-config.ts`
58
+ (`PROVIDER_MODELS` / `DEFAULT_MODELS`). Run `pnpm list:models` to discover the
59
+ models a provider currently exposes.
60
+
61
+ ## Documentation
62
+
63
+ For the full architecture overview, the richtext/AI integration, and getting
64
+ started instructions, see the main repository:
65
+ <https://github.com/Byline-CMS/bylinecms.dev>. Relevant docs: `docs/RICHTEXT.md`,
66
+ `docs/FIELDS.md`, and `docs/MCP.md`.
67
+
68
+ ## License
69
+
70
+ MPL-2.0
@@ -0,0 +1,16 @@
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
+ export declare const TOGGLE_AI_DRAWER_COMMAND: import("lexical").LexicalCommand<unknown>;
9
+ /**
10
+ * Broadcasts the AI drawer's open/closed state so contributed UI (the
11
+ * toolbar button) can show an active visual cue. Dispatched by the drawer
12
+ * plugin whenever `open` changes — including closes triggered from the
13
+ * drawer's own controls. Observers register a listener and return `false`.
14
+ */
15
+ export declare const AI_DRAWER_STATE_COMMAND: import("lexical").LexicalCommand<boolean>;
16
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../src/plugins/lexical/commands.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,eAAO,MAAM,wBAAwB,2CAA4C,CAAA;AAEjF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,2CAAoD,CAAA"}
@@ -0,0 +1,23 @@
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
+ * Lexical commands for the AI drawer, split out of `plugin.tsx` so the
10
+ * extension definition (and any toolbar contribution) can reference them
11
+ * without statically importing the drawer's React component graph. This
12
+ * keeps `AiLexicalExtension` light enough to be referenced from an
13
+ * eagerly-evaluated admin/client config.
14
+ */
15
+ import { createCommand } from 'lexical';
16
+ export const TOGGLE_AI_DRAWER_COMMAND = createCommand('TOGGLE_AI_DRAWER_COMMAND');
17
+ /**
18
+ * Broadcasts the AI drawer's open/closed state so contributed UI (the
19
+ * toolbar button) can show an active visual cue. Dispatched by the drawer
20
+ * plugin whenever `open` changes — including closes triggered from the
21
+ * drawer's own controls. Observers register a listener and return `false`.
22
+ */
23
+ export const AI_DRAWER_STATE_COMMAND = createCommand('AI_DRAWER_STATE_COMMAND');
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../../../src/plugins/lexical/extension.tsx"],"names":[],"mappings":"AA8DA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,mHAiB7B,CAAA"}
1
+ {"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../../../src/plugins/lexical/extension.tsx"],"names":[],"mappings":"AA0EA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,mHAyB7B,CAAA"}
@@ -7,12 +7,23 @@ import { jsx as _jsx } from "react/jsx-runtime";
7
7
  *
8
8
  * Copyright (c) Infonomic Company Limited
9
9
  */
10
- import { useEffect, useState } from 'react';
11
- import { BylineToolbarExtension, useToolbarActiveEditor, } from '@byline/richtext-lexical';
10
+ import { lazy, Suspense, useEffect, useState } from 'react';
11
+ import { BylineToolbarExtension, useToolbarActiveEditor, } from '@byline/richtext-lexical/config';
12
12
  import { AiIcon } from '@byline/ui/react';
13
13
  import { ReactExtension } from '@lexical/react/ReactExtension';
14
14
  import { COMMAND_PRIORITY_LOW, configExtension, declarePeerDependency, defineExtension, } from 'lexical';
15
- import { AI_DRAWER_STATE_COMMAND, AiPluginLexical, TOGGLE_AI_DRAWER_COMMAND } from './plugin';
15
+ import { AI_DRAWER_STATE_COMMAND, TOGGLE_AI_DRAWER_COMMAND } from './commands';
16
+ /**
17
+ * The AI drawer is dynamic-imported so that merely *referencing*
18
+ * `AiLexicalExtension` (e.g. from an eagerly-evaluated admin/client
19
+ * config) does not pull the drawer's React component graph — and its
20
+ * Anthropic/OpenAI/Google-adjacent imports — into the importing bundle.
21
+ * The chunk loads on first editor mount, behind the editor's own lazy
22
+ * boundary; the inner `Suspense` is a belt-and-braces fallback.
23
+ */
24
+ const AiPluginLexical = lazy(async () => ({
25
+ default: (await import('./plugin')).AiPluginLexical,
26
+ }));
16
27
  function AiToolbarButton() {
17
28
  const editor = useToolbarActiveEditor();
18
29
  const [isOpen, setIsOpen] = useState(false);
@@ -42,7 +53,13 @@ function AiToolbarButton() {
42
53
  */
43
54
  export const AiLexicalExtension = defineExtension({
44
55
  name: '@byline/ai/Lexical',
45
- dependencies: [configExtension(ReactExtension, { decorators: [_jsx(AiPluginLexical, {}, "d")] })],
56
+ dependencies: [
57
+ configExtension(ReactExtension, {
58
+ decorators: [
59
+ _jsx(Suspense, { fallback: null, children: _jsx(AiPluginLexical, {}) }, "d"),
60
+ ],
61
+ }),
62
+ ],
46
63
  peerDependencies: [
47
64
  declarePeerDependency(BylineToolbarExtension.name, {
48
65
  items: [
@@ -5,6 +5,6 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
+ export { TOGGLE_AI_DRAWER_COMMAND } from './commands';
8
9
  export { AiLexicalExtension } from './extension';
9
- export { AiPluginLexical, TOGGLE_AI_DRAWER_COMMAND } from './plugin';
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/lexical/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/lexical/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA"}
@@ -5,5 +5,5 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
+ export { TOGGLE_AI_DRAWER_COMMAND } from './commands';
8
9
  export { AiLexicalExtension } from './extension';
9
- export { AiPluginLexical, TOGGLE_AI_DRAWER_COMMAND } from './plugin';
@@ -6,13 +6,5 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
  import * as React from 'react';
9
- export declare const TOGGLE_AI_DRAWER_COMMAND: import("lexical").LexicalCommand<unknown>;
10
- /**
11
- * Broadcasts the AI drawer's open/closed state so contributed UI (the
12
- * toolbar button) can show an active visual cue. Dispatched by the drawer
13
- * plugin whenever `open` changes — including closes triggered from the
14
- * drawer's own controls. Observers register a listener and return `false`.
15
- */
16
- export declare const AI_DRAWER_STATE_COMMAND: import("lexical").LexicalCommand<boolean>;
17
9
  export declare const AiPluginLexical: React.MemoExoticComponent<() => React.JSX.Element | undefined>;
18
10
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/lexical/plugin.tsx"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqB9B,eAAO,MAAM,wBAAwB,2CAA4C,CAAA;AAEjF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,2CAAoD,CAAA;AAUxF,eAAO,MAAM,eAAe,kCAAmC,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,SAAS,CAyY1F,CAAA"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/lexical/plugin.tsx"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA6B9B,eAAO,MAAM,eAAe,kCAAmC,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,SAAS,CAyY1F,CAAA"}
@@ -13,19 +13,12 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
13
13
  import { mergeRegister } from '@lexical/utils';
14
14
  import {
15
15
  // CLEAR_EDITOR_COMMAND,
16
- COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_NORMAL, createCommand, SELECTION_CHANGE_COMMAND, } from 'lexical';
16
+ COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_NORMAL, SELECTION_CHANGE_COMMAND, } from 'lexical';
17
17
  import { useAiPublicConfig } from '../../config/ai-provider';
18
18
  import { AiPluginBase } from '../ai-plugin-base';
19
+ import { AI_DRAWER_STATE_COMMAND, TOGGLE_AI_DRAWER_COMMAND } from './commands';
19
20
  import { createEmptyEditorState } from './create-empty-editor-state';
20
21
  import { importHtmlToSerializedEditorState } from './import-html';
21
- export const TOGGLE_AI_DRAWER_COMMAND = createCommand('TOGGLE_AI_DRAWER_COMMAND');
22
- /**
23
- * Broadcasts the AI drawer's open/closed state so contributed UI (the
24
- * toolbar button) can show an active visual cue. Dispatched by the drawer
25
- * plugin whenever `open` changes — including closes triggered from the
26
- * drawer's own controls. Observers register a listener and return `false`.
27
- */
28
- export const AI_DRAWER_STATE_COMMAND = createCommand('AI_DRAWER_STATE_COMMAND');
29
22
  const emptyInstructionState = {
30
23
  prompt: '',
31
24
  editor: null,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/ai",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "3.0.2",
5
+ "version": "3.1.1",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -66,9 +66,9 @@
66
66
  "react-error-boundary": "^6.1.1",
67
67
  "uuid": "^14.0.0",
68
68
  "zod": "^4.4.3",
69
- "@byline/richtext-lexical": "3.0.2",
70
- "@byline/core": "3.0.2",
71
- "@byline/ui": "3.0.2"
69
+ "@byline/core": "3.1.1",
70
+ "@byline/ui": "3.1.1",
71
+ "@byline/richtext-lexical": "3.1.1"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@biomejs/biome": "2.4.15",