@stll/chat 0.0.1-placeholder.0 → 0.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 ADDED
@@ -0,0 +1,28 @@
1
+ # `@stll/chat`
2
+
3
+ Portable React chat primitives: a rich-editor composer shell, message stream,
4
+ model selector, and observable streaming runtime.
5
+
6
+ The host owns authentication, endpoint selection, persistence, provider
7
+ credentials, translations, and rich-message rendering. It supplies a typed
8
+ `ChatTransport`; the package never reads environment variables or silently
9
+ selects a provider.
10
+
11
+ ```tsx
12
+ import { ChatComposer, ChatMessageStream, createChatRuntime } from "@stll/chat";
13
+
14
+ const runtime = createChatRuntime({ transport });
15
+
16
+ <ChatComposer
17
+ canSend={draft.length > 0}
18
+ isGenerating={runtime.getSnapshot().isStreaming}
19
+ labels={{ retry: "Retry", send: "Send", stop: "Stop" }}
20
+ onSend={sendDraft}
21
+ >
22
+ <RichEditor />
23
+ </ChatComposer>;
24
+ ```
25
+
26
+ Validate every explicit provider/model selection with
27
+ `resolveChatModelSelection`. It rejects unknown, empty, duplicate, and
28
+ unconfigured provider/model combinations with `ChatConfigurationError`.
@@ -0,0 +1,31 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/composer.d.ts
3
+ type ChatComposerActionState = {
4
+ canSend: boolean;
5
+ isGenerating: boolean;
6
+ onRetry?: (() => void) | undefined;
7
+ onSend: () => void;
8
+ onStop?: (() => void) | undefined;
9
+ };
10
+ type ChatComposerActionMode = "retry" | "send" | "stop";
11
+ declare const resolveChatComposerAction: ({ isGenerating, onRetry, onStop }: Pick<ChatComposerActionState, "isGenerating" | "onRetry" | "onStop">) => ChatComposerActionMode;
12
+ type ChatComposerLabels = Record<ChatComposerActionMode, string>;
13
+ declare const ChatComposerAction: ({ canSend, className, isGenerating, labels, onRetry, onSend, onStop }: ChatComposerActionState & {
14
+ className?: string | undefined;
15
+ labels: ChatComposerLabels;
16
+ }) => import("react").JSX.Element;
17
+ type ChatComposerProps = ChatComposerActionState & {
18
+ children: ReactNode;
19
+ className?: string | undefined;
20
+ labels: ChatComposerLabels;
21
+ leading?: ReactNode | undefined;
22
+ status?: ReactNode | undefined;
23
+ };
24
+ /**
25
+ * Layout-only composer shell for rich editors. `children` is deliberately a
26
+ * host-owned editor slot, so TipTap, ProseMirror, native inputs, and mobile
27
+ * editors can share send/stop/retry behavior without copying shell chrome.
28
+ */
29
+ declare const ChatComposer: ({ children, className, labels, leading, status, ...action }: ChatComposerProps) => import("react").JSX.Element;
30
+ //#endregion
31
+ export { ChatComposer, ChatComposerAction, ChatComposerActionMode, ChatComposerActionState, ChatComposerLabels, ChatComposerProps, resolveChatComposerAction };
@@ -0,0 +1,78 @@
1
+ import { ArrowUpIcon, RotateCcwIcon, SquareIcon } from "lucide-react";
2
+ import { Button } from "@stll/ui/button";
3
+ import { cn } from "@stll/ui/utils";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/composer.tsx
6
+ const resolveChatComposerAction = ({ isGenerating, onRetry, onStop }) => {
7
+ if (isGenerating && onStop !== void 0) return "stop";
8
+ if (!isGenerating && onRetry !== void 0) return "retry";
9
+ return "send";
10
+ };
11
+ const ChatComposerAction = ({ canSend, className, isGenerating, labels, onRetry, onSend, onStop }) => {
12
+ const mode = resolveChatComposerAction({
13
+ isGenerating,
14
+ onRetry,
15
+ onStop
16
+ });
17
+ const disabled = mode === "send" && !canSend;
18
+ const onClick = () => {
19
+ switch (mode) {
20
+ case "send":
21
+ onSend();
22
+ return;
23
+ case "stop":
24
+ onStop?.();
25
+ return;
26
+ case "retry":
27
+ onRetry?.();
28
+ return;
29
+ }
30
+ };
31
+ return /* @__PURE__ */ jsxs(Button, {
32
+ "aria-label": labels[mode],
33
+ className: cn("bg-foreground text-background hover:bg-foreground/90 size-7 shrink-0 rounded-full", className),
34
+ disabled,
35
+ onClick,
36
+ size: "icon-sm",
37
+ tooltip: false,
38
+ type: "button",
39
+ children: [
40
+ mode === "send" && /* @__PURE__ */ jsx(ArrowUpIcon, {
41
+ "aria-hidden": "true",
42
+ className: "size-3.5"
43
+ }),
44
+ mode === "stop" && /* @__PURE__ */ jsx(SquareIcon, {
45
+ "aria-hidden": "true",
46
+ className: "size-3.5"
47
+ }),
48
+ mode === "retry" && /* @__PURE__ */ jsx(RotateCcwIcon, {
49
+ "aria-hidden": "true",
50
+ className: "size-3.5"
51
+ })
52
+ ]
53
+ });
54
+ };
55
+ /**
56
+ * Layout-only composer shell for rich editors. `children` is deliberately a
57
+ * host-owned editor slot, so TipTap, ProseMirror, native inputs, and mobile
58
+ * editors can share send/stop/retry behavior without copying shell chrome.
59
+ */
60
+ const ChatComposer = ({ children, className, labels, leading, status, ...action }) => /* @__PURE__ */ jsxs("section", {
61
+ className: cn("flex flex-col gap-1.5", className),
62
+ children: [/* @__PURE__ */ jsxs("div", {
63
+ className: "border-input bg-background flex min-w-0 items-end gap-2 rounded-xl border p-2",
64
+ children: [
65
+ leading,
66
+ /* @__PURE__ */ jsx("div", {
67
+ className: "min-w-0 flex-1",
68
+ children
69
+ }),
70
+ /* @__PURE__ */ jsx(ChatComposerAction, {
71
+ ...action,
72
+ labels
73
+ })
74
+ ]
75
+ }), status]
76
+ });
77
+ //#endregion
78
+ export { ChatComposer, ChatComposerAction, resolveChatComposerAction };
@@ -0,0 +1,5 @@
1
+ import { ChatComposer, ChatComposerAction, ChatComposerActionMode, ChatComposerActionState, ChatComposerLabels, ChatComposerProps, resolveChatComposerAction } from "./composer.js";
2
+ import { ChatConfigurationError, ChatMessage, ChatModelOption, ChatModelSelection, ChatProviderConfiguration, ChatRuntime, ChatRuntimeSnapshot, ChatStreamError, ChatStreamEvent, ChatTransport, createChatRuntime, resolveChatModelSelection } from "./runtime.js";
3
+ import { ChatModelSelector, ChatModelSelectorProps, compactModelDisplayName, groupReasoningEfforts, modelSelectionLabel } from "./model-selector.js";
4
+ import { ChatMessageStream, ChatMessageStreamProps } from "./stream.js";
5
+ export { ChatComposer, ChatComposerAction, type ChatComposerActionMode, type ChatComposerActionState, type ChatComposerLabels, type ChatComposerProps, ChatConfigurationError, type ChatMessage, ChatMessageStream, type ChatMessageStreamProps, type ChatModelOption, type ChatModelSelection, ChatModelSelector, type ChatModelSelectorProps, type ChatProviderConfiguration, type ChatRuntime, type ChatRuntimeSnapshot, ChatStreamError, type ChatStreamEvent, type ChatTransport, compactModelDisplayName, createChatRuntime, groupReasoningEfforts, modelSelectionLabel, resolveChatComposerAction, resolveChatModelSelection };
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { ChatComposer, ChatComposerAction, resolveChatComposerAction } from "./composer.js";
2
+ import { ChatConfigurationError, ChatStreamError, createChatRuntime, resolveChatModelSelection } from "./runtime.js";
3
+ import { ChatModelSelector, compactModelDisplayName, groupReasoningEfforts, modelSelectionLabel } from "./model-selector.js";
4
+ import { ChatMessageStream } from "./stream.js";
5
+ export { ChatComposer, ChatComposerAction, ChatConfigurationError, ChatMessageStream, ChatModelSelector, ChatStreamError, compactModelDisplayName, createChatRuntime, groupReasoningEfforts, modelSelectionLabel, resolveChatComposerAction, resolveChatModelSelection };
@@ -0,0 +1,32 @@
1
+ import { ChatModelOption, ChatModelSelection } from "./runtime.js";
2
+ import { ReasoningEffort } from "@stll/ai-catalog";
3
+ //#region src/model-selector.d.ts
4
+ declare const compactModelDisplayName: (displayName: string) => string;
5
+ declare const groupReasoningEfforts: (efforts: readonly ReasoningEffort[]) => {
6
+ extended: ReasoningEffort[];
7
+ standard: ReasoningEffort[];
8
+ };
9
+ declare const modelSelectionLabel: ({ defaultEffortLabel, displayName, formatSelectionLabel, providerDefaultEffort, reasoningEffort, translateEffort }: {
10
+ defaultEffortLabel: string;
11
+ displayName: string;
12
+ formatSelectionLabel: (values: {
13
+ effort: string;
14
+ model: string;
15
+ }) => string;
16
+ providerDefaultEffort: ReasoningEffort | null;
17
+ reasoningEffort: ReasoningEffort | null;
18
+ translateEffort: (effort: ReasoningEffort) => string;
19
+ }) => string;
20
+ type ChatModelSelectorProps = {
21
+ autoLabel: string;
22
+ disabled?: boolean | undefined;
23
+ label: string;
24
+ onSelectionChange: (selection: ChatModelSelection) => void;
25
+ options: readonly ChatModelOption[];
26
+ selection: ChatModelSelection;
27
+ };
28
+ /** Native, accessible model control. Provider configuration is validated by
29
+ * `resolveChatModelSelection` before a host accepts the new selection. */
30
+ declare const ChatModelSelector: ({ autoLabel, disabled, label, onSelectionChange, options, selection }: ChatModelSelectorProps) => import("react").JSX.Element;
31
+ //#endregion
32
+ export { ChatModelSelector, ChatModelSelectorProps, compactModelDisplayName, groupReasoningEfforts, modelSelectionLabel };
@@ -0,0 +1,77 @@
1
+ import { ChatConfigurationError } from "./runtime.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/model-selector.tsx
4
+ const CLAUDE_FAMILY_PREFIX = /^Claude\s+/u;
5
+ const compactModelDisplayName = (displayName) => displayName.replace(CLAUDE_FAMILY_PREFIX, "");
6
+ const effortSection = {
7
+ none: "standard",
8
+ minimal: "standard",
9
+ low: "standard",
10
+ medium: "standard",
11
+ high: "standard",
12
+ xhigh: "extended",
13
+ max: "extended"
14
+ };
15
+ const groupReasoningEfforts = (efforts) => {
16
+ const standard = [];
17
+ const extended = [];
18
+ for (const effort of efforts) {
19
+ if (effortSection[effort] === "extended") {
20
+ extended.push(effort);
21
+ continue;
22
+ }
23
+ standard.push(effort);
24
+ }
25
+ return {
26
+ extended,
27
+ standard
28
+ };
29
+ };
30
+ const modelSelectionLabel = ({ defaultEffortLabel, displayName, formatSelectionLabel, providerDefaultEffort, reasoningEffort, translateEffort }) => {
31
+ const effectiveEffort = reasoningEffort ?? providerDefaultEffort;
32
+ return formatSelectionLabel({
33
+ effort: effectiveEffort === null ? defaultEffortLabel : translateEffort(effectiveEffort),
34
+ model: compactModelDisplayName(displayName)
35
+ });
36
+ };
37
+ const toValue = ({ modelId, provider }) => modelId === null || provider === null ? "" : `${provider}:${modelId}`;
38
+ const parseSelection = (value, options) => {
39
+ if (value.length === 0) return {
40
+ modelId: null,
41
+ provider: null,
42
+ reasoningEffort: null
43
+ };
44
+ const option = options.find((candidate) => `${candidate.provider}:${candidate.modelId}` === value);
45
+ if (option === void 0) throw new ChatConfigurationError({
46
+ code: "unconfigured-model",
47
+ message: `Selected AI model is not available: ${value}.`,
48
+ modelId: value
49
+ });
50
+ return {
51
+ modelId: option.modelId,
52
+ provider: option.provider,
53
+ reasoningEffort: null
54
+ };
55
+ };
56
+ /** Native, accessible model control. Provider configuration is validated by
57
+ * `resolveChatModelSelection` before a host accepts the new selection. */
58
+ const ChatModelSelector = ({ autoLabel, disabled = false, label, onSelectionChange, options, selection }) => {
59
+ const onChange = ({ currentTarget }) => {
60
+ onSelectionChange(parseSelection(currentTarget.value, options));
61
+ };
62
+ return /* @__PURE__ */ jsxs("select", {
63
+ "aria-label": label,
64
+ disabled,
65
+ onChange,
66
+ value: toValue(selection),
67
+ children: [/* @__PURE__ */ jsx("option", {
68
+ value: "",
69
+ children: autoLabel
70
+ }), options.map((option) => /* @__PURE__ */ jsx("option", {
71
+ value: `${option.provider}:${option.modelId}`,
72
+ children: option.displayName
73
+ }, `${option.provider}:${option.modelId}`))]
74
+ });
75
+ };
76
+ //#endregion
77
+ export { ChatModelSelector, compactModelDisplayName, groupReasoningEfforts, modelSelectionLabel };
@@ -0,0 +1,78 @@
1
+ import { AIProvider, ReasoningEffort } from "@stll/ai-catalog";
2
+ //#region src/runtime.d.ts
3
+ type ChatModelOption = {
4
+ defaultReasoningEffort: ReasoningEffort | null;
5
+ displayName: string;
6
+ modelId: string;
7
+ provider: AIProvider;
8
+ reasoningEfforts: readonly ReasoningEffort[] | null;
9
+ };
10
+ type ChatModelSelection = {
11
+ modelId: string | null;
12
+ provider: AIProvider | null;
13
+ reasoningEffort: ReasoningEffort | null;
14
+ };
15
+ type ChatProviderConfiguration = {
16
+ modelIds: readonly string[];
17
+ provider: AIProvider;
18
+ };
19
+ declare const ChatConfigurationError_base: import("better-result").TaggedErrorClass<"ChatConfigurationError">;
20
+ declare class ChatConfigurationError extends ChatConfigurationError_base<{
21
+ code: "duplicate-model" | "empty-model-id" | "empty-provider" | "unknown-provider" | "unconfigured-model";
22
+ message: string;
23
+ modelId?: string | undefined;
24
+ provider?: string | undefined;
25
+ }> {}
26
+ declare const ChatStreamError_base: import("better-result").TaggedErrorClass<"ChatStreamError">;
27
+ declare class ChatStreamError extends ChatStreamError_base<{
28
+ cause: unknown;
29
+ message: string;
30
+ }> {}
31
+ /**
32
+ * Validate provider configuration at the shell boundary. A missing or unknown
33
+ * provider/model is an explicit typed error: the runtime never guesses a
34
+ * provider or silently routes a request somewhere else.
35
+ */
36
+ declare const resolveChatModelSelection: ({ providers, selection }: {
37
+ providers: readonly ChatProviderConfiguration[];
38
+ selection: ChatModelSelection;
39
+ }) => ChatModelSelection;
40
+ type ChatMessage<Metadata = undefined> = {
41
+ content: string;
42
+ id: string;
43
+ metadata: Metadata;
44
+ role: "assistant" | "system" | "user";
45
+ };
46
+ type ChatStreamEvent<Metadata = undefined> = {
47
+ message: ChatMessage<Metadata>;
48
+ type: "message";
49
+ } | {
50
+ message: ChatMessage<Metadata>;
51
+ type: "replace-message";
52
+ };
53
+ type ChatTransport<Metadata = undefined> = (input: {
54
+ messages: readonly ChatMessage<Metadata>[];
55
+ signal: AbortSignal;
56
+ }) => AsyncIterable<ChatStreamEvent<Metadata>>;
57
+ type ChatRuntimeSnapshot<Metadata = undefined> = {
58
+ error: ChatStreamError | undefined;
59
+ isStreaming: boolean;
60
+ messages: readonly ChatMessage<Metadata>[];
61
+ };
62
+ type ChatRuntime<Metadata = undefined> = {
63
+ getSnapshot: () => ChatRuntimeSnapshot<Metadata>;
64
+ send: (message: ChatMessage<Metadata>) => Promise<void>;
65
+ stop: () => void;
66
+ subscribe: (listener: () => void) => () => void;
67
+ };
68
+ /**
69
+ * A framework-neutral stream runtime. Hosts own authentication, endpoint
70
+ * selection, persistence, and provider credentials through `transport`; this
71
+ * primitive owns the observable stream lifecycle and makes failures explicit.
72
+ */
73
+ declare const createChatRuntime: <Metadata = undefined>({ initialMessages, transport }: {
74
+ initialMessages?: readonly ChatMessage<Metadata>[];
75
+ transport: ChatTransport<Metadata>;
76
+ }) => ChatRuntime<Metadata>;
77
+ //#endregion
78
+ export { ChatConfigurationError, ChatMessage, ChatModelOption, ChatModelSelection, ChatProviderConfiguration, ChatRuntime, ChatRuntimeSnapshot, ChatStreamError, ChatStreamEvent, ChatTransport, createChatRuntime, resolveChatModelSelection };
@@ -0,0 +1,144 @@
1
+ import { TaggedError } from "better-result";
2
+ import { AI_PROVIDERS } from "@stll/ai-catalog";
3
+ //#region src/runtime.ts
4
+ var ChatConfigurationError = class extends TaggedError("ChatConfigurationError") {};
5
+ var ChatStreamError = class extends TaggedError("ChatStreamError") {};
6
+ const providerSet = new Set(AI_PROVIDERS);
7
+ const modelKey = (provider, modelId) => `${provider}:${modelId}`;
8
+ /**
9
+ * Validate provider configuration at the shell boundary. A missing or unknown
10
+ * provider/model is an explicit typed error: the runtime never guesses a
11
+ * provider or silently routes a request somewhere else.
12
+ */
13
+ const resolveChatModelSelection = ({ providers, selection }) => {
14
+ const configuredModels = /* @__PURE__ */ new Set();
15
+ for (const provider of providers) {
16
+ if (!providerSet.has(provider.provider)) throw new ChatConfigurationError({
17
+ code: "unknown-provider",
18
+ message: `Unknown AI provider: ${provider.provider}.`,
19
+ provider: provider.provider
20
+ });
21
+ if (provider.modelIds.length === 0) throw new ChatConfigurationError({
22
+ code: "empty-provider",
23
+ message: `AI provider ${provider.provider} has no configured models.`,
24
+ provider: provider.provider
25
+ });
26
+ for (const modelId of provider.modelIds) {
27
+ if (modelId.trim().length === 0) throw new ChatConfigurationError({
28
+ code: "empty-model-id",
29
+ message: `AI provider ${provider.provider} has an empty model ID.`,
30
+ provider: provider.provider
31
+ });
32
+ const key = modelKey(provider.provider, modelId);
33
+ if (configuredModels.has(key)) throw new ChatConfigurationError({
34
+ code: "duplicate-model",
35
+ message: `AI model ${modelId} is configured more than once for ${provider.provider}.`,
36
+ modelId,
37
+ provider: provider.provider
38
+ });
39
+ configuredModels.add(key);
40
+ }
41
+ }
42
+ if (selection.modelId === null || selection.provider === null) {
43
+ if (selection.modelId === null && selection.provider === null) return selection;
44
+ throw new ChatConfigurationError({
45
+ code: "unconfigured-model",
46
+ message: "A model selection must include both provider and model ID.",
47
+ modelId: selection.modelId ?? void 0,
48
+ provider: selection.provider ?? void 0
49
+ });
50
+ }
51
+ if (!configuredModels.has(modelKey(selection.provider, selection.modelId))) throw new ChatConfigurationError({
52
+ code: "unconfigured-model",
53
+ message: `AI model ${selection.modelId} is not configured for ${selection.provider}.`,
54
+ modelId: selection.modelId,
55
+ provider: selection.provider
56
+ });
57
+ return selection;
58
+ };
59
+ const toStreamError = (error) => error instanceof ChatStreamError ? error : new ChatStreamError({
60
+ cause: error,
61
+ message: error instanceof Error ? error.message : "Chat stream failed."
62
+ });
63
+ /**
64
+ * A framework-neutral stream runtime. Hosts own authentication, endpoint
65
+ * selection, persistence, and provider credentials through `transport`; this
66
+ * primitive owns the observable stream lifecycle and makes failures explicit.
67
+ */
68
+ const createChatRuntime = ({ initialMessages = [], transport }) => {
69
+ const listeners = /* @__PURE__ */ new Set();
70
+ let abortController;
71
+ let snapshot = {
72
+ error: void 0,
73
+ isStreaming: false,
74
+ messages: initialMessages
75
+ };
76
+ const emit = () => {
77
+ for (const listener of listeners) listener();
78
+ };
79
+ const update = (next) => {
80
+ snapshot = next;
81
+ emit();
82
+ };
83
+ return {
84
+ getSnapshot: () => snapshot,
85
+ send: async (message) => {
86
+ if (snapshot.isStreaming) throw new ChatStreamError({
87
+ cause: void 0,
88
+ message: "A chat stream is already active."
89
+ });
90
+ const controller = new AbortController();
91
+ abortController = controller;
92
+ update({
93
+ error: void 0,
94
+ isStreaming: true,
95
+ messages: [...snapshot.messages, message]
96
+ });
97
+ try {
98
+ for await (const event of transport({
99
+ messages: snapshot.messages,
100
+ signal: controller.signal
101
+ })) {
102
+ if (abortController !== controller) return;
103
+ const messages = event.type === "message" ? [...snapshot.messages, event.message] : snapshot.messages.map((candidate) => candidate.id === event.message.id ? event.message : candidate);
104
+ update({
105
+ error: void 0,
106
+ isStreaming: true,
107
+ messages
108
+ });
109
+ }
110
+ if (abortController === controller) {
111
+ abortController = void 0;
112
+ update({
113
+ ...snapshot,
114
+ isStreaming: false
115
+ });
116
+ }
117
+ } catch (error) {
118
+ if (abortController !== controller) return;
119
+ abortController = void 0;
120
+ update({
121
+ error: toStreamError(error),
122
+ isStreaming: false,
123
+ messages: snapshot.messages
124
+ });
125
+ }
126
+ },
127
+ stop: () => {
128
+ abortController?.abort();
129
+ abortController = void 0;
130
+ if (snapshot.isStreaming) update({
131
+ ...snapshot,
132
+ isStreaming: false
133
+ });
134
+ },
135
+ subscribe: (listener) => {
136
+ listeners.add(listener);
137
+ return () => {
138
+ listeners.delete(listener);
139
+ };
140
+ }
141
+ };
142
+ };
143
+ //#endregion
144
+ export { ChatConfigurationError, ChatStreamError, createChatRuntime, resolveChatModelSelection };
@@ -0,0 +1,14 @@
1
+ import { ChatMessage } from "./runtime.js";
2
+ import { ReactElement } from "react";
3
+ //#region src/stream.d.ts
4
+ type ChatMessageStreamProps<Metadata> = {
5
+ className?: string | undefined;
6
+ empty?: ReactElement | undefined;
7
+ messages: readonly ChatMessage<Metadata>[];
8
+ renderMessage: (message: ChatMessage<Metadata>) => ReactElement;
9
+ };
10
+ /** A semantic transcript container; hosts supply their own rich markdown,
11
+ * citation, tool-call, and attachment renderers through `renderMessage`. */
12
+ declare const ChatMessageStream: <Metadata>({ className, empty, messages, renderMessage }: ChatMessageStreamProps<Metadata>) => ReactElement | null;
13
+ //#endregion
14
+ export { ChatMessageStream, ChatMessageStreamProps };
package/dist/stream.js ADDED
@@ -0,0 +1,13 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ //#region src/stream.tsx
3
+ /** A semantic transcript container; hosts supply their own rich markdown,
4
+ * citation, tool-call, and attachment renderers through `renderMessage`. */
5
+ const ChatMessageStream = ({ className, empty, messages, renderMessage }) => {
6
+ if (messages.length === 0) return empty ?? null;
7
+ return /* @__PURE__ */ jsx("ol", {
8
+ className,
9
+ children: messages.map((message) => /* @__PURE__ */ jsx("li", { children: renderMessage(message) }, message.id))
10
+ });
11
+ };
12
+ //#endregion
13
+ export { ChatMessageStream };
package/package.json CHANGED
@@ -1,6 +1,87 @@
1
1
  {
2
2
  "name": "@stll/chat",
3
- "version": "0.0.1-placeholder.0",
4
- "description": "Placeholder to bootstrap npm trusted publishing. Real releases are published from stella/stella via GitHub Actions.",
5
- "license": "Apache-2.0"
3
+ "version": "0.1.1",
4
+ "description": "Typed React chat composer, message stream, model selector, and transport runtime primitives.",
5
+ "keywords": [
6
+ "ai",
7
+ "chat",
8
+ "react",
9
+ "streaming"
10
+ ],
11
+ "homepage": "https://github.com/stella/stella/tree/main/packages/chat",
12
+ "bugs": {
13
+ "url": "https://github.com/stella/stella/issues"
14
+ },
15
+ "license": "Apache-2.0",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/stella/stella.git",
19
+ "directory": "packages/chat"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./composer": {
33
+ "types": "./dist/composer.d.ts",
34
+ "import": "./dist/composer.js"
35
+ },
36
+ "./model-selector": {
37
+ "types": "./dist/model-selector.d.ts",
38
+ "import": "./dist/model-selector.js"
39
+ },
40
+ "./runtime": {
41
+ "types": "./dist/runtime.d.ts",
42
+ "import": "./dist/runtime.js"
43
+ },
44
+ "./stream": {
45
+ "types": "./dist/stream.d.ts",
46
+ "import": "./dist/stream.js"
47
+ }
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "scripts": {
53
+ "clean": "git clean -xdf dist .cache .turbo node_modules",
54
+ "build": "tsdown",
55
+ "pack:dry-run": "bun pm pack --dry-run",
56
+ "test": "bun test src",
57
+ "typecheck": "bun ../../packages/scripts/src/tsc-native.ts --noEmit",
58
+ "lint": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --deny-warnings --type-aware packages/chat",
59
+ "lint:fix": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --type-aware --fix packages/chat",
60
+ "format": "oxfmt .",
61
+ "prepack": "bun run build"
62
+ },
63
+ "dependencies": {
64
+ "@stll/ai-catalog": "^0.1.4",
65
+ "@stll/ui": "^0.12.0",
66
+ "better-result": "3.0.0",
67
+ "lucide-react": "1.30.0"
68
+ },
69
+ "devDependencies": {
70
+ "@stll/typescript-config": "0.0.0",
71
+ "@types/react": "^19.2.17",
72
+ "@types/react-dom": "^19.2.3",
73
+ "bun-types": "1.4.0",
74
+ "react": "^19.2.8",
75
+ "react-dom": "^19.2.8",
76
+ "tailwindcss": "4.3.3",
77
+ "tsdown": "0.22.14"
78
+ },
79
+ "peerDependencies": {
80
+ "@base-ui/react": "^1.7.0",
81
+ "react": ">=19",
82
+ "react-dom": ">=19",
83
+ "tailwindcss": "^4.3.0"
84
+ },
85
+ "main": "./dist/index.js",
86
+ "types": "./dist/index.d.ts"
6
87
  }