lemma-sdk 0.2.9 → 0.2.11

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
@@ -47,6 +47,64 @@ const supportAssistant = await client.assistants.get("support_assistant");
47
47
  - Ergonomic type aliases exported at top level: `Agent`, `Assistant`, `Conversation`, `Task`, `TaskMessage`, `CreateAgentInput`, `CreateAssistantInput`, etc.
48
48
  - `client.withPod(podId)` returns a pod-scoped client that shares auth state with the parent client.
49
49
 
50
+ ## Table Access Grants (`accessible_tables`)
51
+
52
+ For function, agent, and assistant payloads, `accessible_tables` must be an array of objects:
53
+
54
+ - `table_name`: target table
55
+ - `mode`: `READ` or `WRITE`
56
+
57
+ `accessible_tables: ["table_name"]` is no longer valid.
58
+
59
+ You do not pass a datastore name in SDK calls. Table and file operations are pod-scoped (`client.tables`, `client.records`, `client.files`) and take table/file identifiers directly.
60
+
61
+ Examples:
62
+
63
+ ```ts
64
+ import {
65
+ TableAccessMode,
66
+ type CreateFunctionRequest,
67
+ type CreateAgentInput,
68
+ type CreateAssistantInput,
69
+ } from "lemma-sdk";
70
+
71
+ const functionPayload: CreateFunctionRequest = {
72
+ name: "expense_summary",
73
+ code: "def handler(ctx):\n return {'ok': True}",
74
+ config: {},
75
+ accessible_tables: [
76
+ { table_name: "expenses", mode: TableAccessMode.READ },
77
+ { table_name: "expense_summaries", mode: TableAccessMode.WRITE },
78
+ ],
79
+ accessible_folders: ["/reports"],
80
+ accessible_applications: [],
81
+ };
82
+
83
+ const agentPayload: CreateAgentInput = {
84
+ name: "expense-summarizer",
85
+ instruction: "Summarize expenses without mutating data.",
86
+ tool_sets: [],
87
+ accessible_tables: [
88
+ { table_name: "expenses", mode: TableAccessMode.READ },
89
+ { table_name: "expense_notes", mode: TableAccessMode.WRITE },
90
+ ],
91
+ accessible_folders: [],
92
+ accessible_applications: [],
93
+ };
94
+
95
+ const assistantPayload: CreateAssistantInput = {
96
+ name: "expense_assistant",
97
+ instruction: "Answer expense questions and save approved notes.",
98
+ tool_sets: [],
99
+ accessible_tables: [
100
+ { table_name: "expenses", mode: TableAccessMode.READ },
101
+ { table_name: "expense_notes", mode: TableAccessMode.WRITE },
102
+ ],
103
+ accessible_folders: ["/notes"],
104
+ accessible_applications: [],
105
+ };
106
+ ```
107
+
50
108
  ## Auth Helpers
51
109
 
52
110
  ```ts
@@ -111,6 +169,60 @@ Notes:
111
169
 
112
170
  ## Assistants + Agent Runs
113
171
 
172
+ ### React assistant controller + primitives
173
+
174
+ `lemma-sdk/react` now exposes the assistant controller plus the reusable UI primitives used by the app shell. A simple integration looks like this:
175
+
176
+ ```tsx
177
+ import {
178
+ MessageGroup,
179
+ PlanSummaryStrip,
180
+ ThinkingIndicator,
181
+ buildDisplayMessageRows,
182
+ getActiveToolBanner,
183
+ latestPlanSummary,
184
+ useAssistantController,
185
+ } from "lemma-sdk/react";
186
+
187
+ function AssistantSurface() {
188
+ const assistant = useAssistantController({
189
+ client,
190
+ assistantId: "support_assistant",
191
+ podId: "pod_123",
192
+ });
193
+
194
+ const rows = buildDisplayMessageRows(assistant.messages);
195
+ const plan = latestPlanSummary(assistant.messages);
196
+ const activeToolBanner = getActiveToolBanner(assistant.messages);
197
+
198
+ return (
199
+ <div>
200
+ {plan ? <PlanSummaryStrip plan={plan} onHide={() => {}} /> : null}
201
+ {activeToolBanner ? <div>{activeToolBanner.summary}</div> : null}
202
+
203
+ {rows.map((row, index) => (
204
+ <MessageGroup
205
+ key={row.id}
206
+ message={row.message}
207
+ conversationId={assistant.activeConversationId}
208
+ onWidgetSendPrompt={(text) => assistant.sendMessage(text)}
209
+ isStreaming={assistant.isActiveConversationRunning && row.sourceIndexes.includes(assistant.messages.length - 1)}
210
+ showAssistantHeader={index === 0 || rows[index - 1]?.message.role !== "assistant"}
211
+ renderMessageContent={({ message }) => <div>{message.content}</div>}
212
+ />
213
+ ))}
214
+
215
+ {assistant.isActiveConversationRunning ? <ThinkingIndicator /> : null}
216
+ </div>
217
+ );
218
+ }
219
+ ```
220
+
221
+ The intended split is:
222
+
223
+ - SDK: `useAssistantController`, message/tool normalization, plan parsing, tool rollups, and assistant UI primitives.
224
+ - App: modal shell, fullscreen behavior, route navigation, workspace/file viewers, and product-specific renderers.
225
+
114
226
  ### Assistant names (resource key)
115
227
 
116
228
  Assistant CRUD is name-based:
@@ -2,6 +2,7 @@ import type { ConversationMessage } from "./types.js";
2
2
  export interface ParsedAssistantStreamEvent {
3
3
  message?: ConversationMessage;
4
4
  status?: string;
5
+ token?: string;
5
6
  }
6
7
  export declare function parseAssistantStreamEvent(value: unknown): ParsedAssistantStreamEvent;
7
8
  export declare function upsertConversationMessage(messages: ConversationMessage[], incoming: ConversationMessage): ConversationMessage[];
@@ -51,6 +51,9 @@ export function parseAssistantStreamEvent(value) {
51
51
  }
52
52
  const eventType = typeof value.type === "string" ? value.type.toLowerCase() : "";
53
53
  const payload = extractPayload(value);
54
+ if (eventType === "token" && typeof payload === "string") {
55
+ return { token: payload };
56
+ }
54
57
  if (eventType === "message" || eventType === "message_added") {
55
58
  const message = toConversationMessage(payload);
56
59
  return message ? { message } : {};
@@ -0,0 +1,79 @@
1
+ import { type ReactNode } from "react";
2
+ import type { AssistantRenderableMessage, AssistantToolInvocation } from "../useAssistantController.js";
3
+ import type { AssistantControllerView, AssistantConversationRenderArgs, AssistantMessageRenderArgs, AssistantPendingFileRenderArgs, AssistantPresentedFileRenderArgs, AssistantToolRenderArgs } from "./assistant-types.js";
4
+ type PlanStatus = "pending" | "in_progress" | "completed";
5
+ export interface PlanStepState {
6
+ step: string;
7
+ status: PlanStatus;
8
+ }
9
+ export interface PlanSummaryState {
10
+ steps: PlanStepState[];
11
+ completedCount: number;
12
+ inProgressCount: number;
13
+ running: boolean;
14
+ activeStep?: string;
15
+ }
16
+ type AskQuestionType = "single_select" | "multi_select" | "rank_priorities";
17
+ export interface AskUserInputQuestion {
18
+ question: string;
19
+ options: string[];
20
+ type: AskQuestionType;
21
+ }
22
+ export interface PendingAskUserInput {
23
+ toolCallId: string;
24
+ messageIndex: number;
25
+ questions: AskUserInputQuestion[];
26
+ }
27
+ export interface DisplayMessageRow {
28
+ id: string;
29
+ message: AssistantRenderableMessage;
30
+ sourceIndexes: number[];
31
+ }
32
+ export interface ActiveToolBanner {
33
+ summary: string;
34
+ activeCount: number;
35
+ }
36
+ export interface AssistantExperienceViewProps {
37
+ controller: AssistantControllerView;
38
+ title?: ReactNode;
39
+ subtitle?: ReactNode;
40
+ placeholder?: string;
41
+ emptyState?: ReactNode;
42
+ draft?: string;
43
+ onDraftChange?: (value: string) => void;
44
+ showConversationList?: boolean;
45
+ onNavigateResource?: (resourceType: string, resourceId: string, meta?: Record<string, unknown>) => void;
46
+ renderConversationLabel?: (args: AssistantConversationRenderArgs) => ReactNode;
47
+ renderMessageContent?: (args: AssistantMessageRenderArgs) => ReactNode;
48
+ renderPresentedFile?: (args: AssistantPresentedFileRenderArgs) => ReactNode;
49
+ renderPendingFile?: (args: AssistantPendingFileRenderArgs) => ReactNode;
50
+ renderToolInvocation?: (args: AssistantToolRenderArgs) => ReactNode;
51
+ }
52
+ export declare function dedupToolInvocations(message: AssistantRenderableMessage): AssistantToolInvocation[];
53
+ export declare function latestPlanSummary(messages: AssistantRenderableMessage[]): PlanSummaryState | null;
54
+ export declare function findPendingAskUserInput(messages: AssistantRenderableMessage[]): PendingAskUserInput | null;
55
+ export declare function formatAskUserInputAnswers(questions: AskUserInputQuestion[], answers: string[][]): string;
56
+ export declare function extractPresentFilePathsFromInvocation(invocation: AssistantToolInvocation): string[];
57
+ export declare function buildDisplayMessageRows(messages: AssistantRenderableMessage[]): DisplayMessageRow[];
58
+ export declare function getActiveToolBanner(messages: AssistantRenderableMessage[]): ActiveToolBanner | null;
59
+ export declare function PlanSummaryStrip({ plan, onHide }: {
60
+ plan: PlanSummaryState;
61
+ onHide: () => void;
62
+ }): import("react/jsx-runtime").JSX.Element;
63
+ export declare function ThinkingIndicator(): import("react/jsx-runtime").JSX.Element | null;
64
+ export declare function EmptyState({ onSendMessage }: {
65
+ onSendMessage: (msg: string) => void;
66
+ }): import("react/jsx-runtime").JSX.Element;
67
+ export declare function MessageGroup({ message, conversationId, onNavigateResource, onWidgetSendPrompt, isStreaming, showAssistantHeader, renderMessageContent, renderPresentedFile, renderToolInvocation, }: {
68
+ message: AssistantRenderableMessage;
69
+ conversationId?: string | null;
70
+ onNavigateResource?: (resourceType: string, resourceId: string, meta?: Record<string, unknown>) => void;
71
+ onWidgetSendPrompt: (text: string) => void | Promise<void>;
72
+ isStreaming: boolean;
73
+ showAssistantHeader: boolean;
74
+ renderMessageContent: (args: AssistantMessageRenderArgs) => ReactNode;
75
+ renderPresentedFile?: (args: AssistantPresentedFileRenderArgs) => ReactNode;
76
+ renderToolInvocation?: (args: AssistantToolRenderArgs) => ReactNode;
77
+ }): import("react/jsx-runtime").JSX.Element;
78
+ export declare function AssistantExperienceView({ controller, title, subtitle, placeholder, emptyState, draft: controlledDraft, onDraftChange, showConversationList, onNavigateResource, renderConversationLabel, renderMessageContent, renderPresentedFile, renderPendingFile, renderToolInvocation, }: AssistantExperienceViewProps): import("react/jsx-runtime").JSX.Element;
79
+ export {};