@lotics/app-sdk 0.43.0 → 0.45.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/AGENTS.md +9 -0
- package/dist/src/agent_stream.d.ts +8 -0
- package/dist/src/agent_stream.js +21 -1
- package/dist/src/ask_ai.d.ts +40 -0
- package/dist/src/ask_ai.js +35 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/router.d.ts +1 -1
- package/dist/src/rpc.d.ts +1 -1
- package/dist/src/rpc.js +4 -0
- package/package.json +4 -4
package/AGENTS.md
CHANGED
|
@@ -112,6 +112,15 @@ Pick by intent. (→ open the `.d.ts` for the exact signature.)
|
|
|
112
112
|
**`openExternal(url)`** opens a link in a new tab (scheme-validated; host-mediated in the embed).
|
|
113
113
|
**`downloadFile(name, data, mime?)`** saves browser-built bytes (a `Uint8Array | Blob | string`) —
|
|
114
114
|
call it synchronously from the click that produced them.
|
|
115
|
+
- **Chat handoff** — **`askAi({ prompt?, file_ids?, record_ids?, context? })`** opens the Lotics
|
|
116
|
+
messenger on a FRESH chat seeded with the given files (attached + previewed beside the chat),
|
|
117
|
+
records (the agent can read/act on them under the viewer's authority), a prefilled *editable*
|
|
118
|
+
prompt, and free-text grounding; the host stamps the app's identity automatically. Nothing runs
|
|
119
|
+
until the user presses send. **The razor**: outcome lands in *fields* → `useAgentRun` + a
|
|
120
|
+
review surface (the app's own structured commit path); outcome is a *file or an open-ended
|
|
121
|
+
answer* (edit this invoice, draft an email about this shipment) → `askAi`. Embedded-only —
|
|
122
|
+
rejects in standalone/dev. Name the task in `prompt` (Word/Excel bytes aren't inlined; a clear
|
|
123
|
+
brief makes the agent read the file first).
|
|
115
124
|
|
|
116
125
|
### Decoding query cells — never hand-roll the serialization contract
|
|
117
126
|
|
|
@@ -31,6 +31,11 @@ export interface AgentRunStep {
|
|
|
31
31
|
output?: unknown;
|
|
32
32
|
/** The tool failure message, when `status` is `"error"`. */
|
|
33
33
|
errorText?: string;
|
|
34
|
+
/** Running count of streamed argument characters. While a tool call's arguments
|
|
35
|
+
* are still generating, the reducer writes a live size into `detail` ("18 KB")
|
|
36
|
+
* off this accumulator, so a long generation reads as progressing, not frozen;
|
|
37
|
+
* both are cleared when the call settles. */
|
|
38
|
+
streamedChars?: number;
|
|
34
39
|
}
|
|
35
40
|
/** One entry in the ordered run transcript, in the order it streamed: the agent's
|
|
36
41
|
* answer prose (`text`), its thinking (`reasoning` — shown collapsed, revealed on
|
|
@@ -68,6 +73,9 @@ export declare function initialAgentRunState(): AgentRunState;
|
|
|
68
73
|
interface Chunk {
|
|
69
74
|
type?: string;
|
|
70
75
|
delta?: string;
|
|
76
|
+
/** Partial tool-argument text, streamed while a tool call's input is generated
|
|
77
|
+
* (`tool-input-delta`) — distinct from `delta` (answer/thinking prose). */
|
|
78
|
+
inputTextDelta?: string;
|
|
71
79
|
toolName?: string;
|
|
72
80
|
toolCallId?: string;
|
|
73
81
|
input?: unknown;
|
package/dist/src/agent_stream.js
CHANGED
|
@@ -23,6 +23,10 @@ const SUBMIT_TOOL = "submit_result";
|
|
|
23
23
|
export function initialAgentRunState() {
|
|
24
24
|
return { status: "streaming", items: [] };
|
|
25
25
|
}
|
|
26
|
+
/** A live size for a step's still-streaming arguments — B under 1 KB, else KB. */
|
|
27
|
+
function formatStreamSize(chars) {
|
|
28
|
+
return chars < 1024 ? `${chars} B` : `${Math.round(chars / 1024)} KB`;
|
|
29
|
+
}
|
|
26
30
|
/** Grow the trailing prose segment of the given kind, or open a new one (after a
|
|
27
31
|
* tool ran, or when the kind flips text↔reasoning) — so the transcript interleaves
|
|
28
32
|
* answer prose, thinking, and tools in the order they streamed. */
|
|
@@ -73,8 +77,24 @@ export function reduceAgentChunk(state, chunk) {
|
|
|
73
77
|
items: [...state.items, { type: "step", id, label: name, kind: "tool", status: "running", input: chunk.input }],
|
|
74
78
|
};
|
|
75
79
|
}
|
|
80
|
+
case "tool-input-delta": {
|
|
81
|
+
// A tool's arguments streaming in — grow the step's live size so a long
|
|
82
|
+
// generation reads as progressing. `submit_result` created no step (it sets
|
|
83
|
+
// `output`), so its deltas find no matching step and this no-ops.
|
|
84
|
+
const len = (chunk.inputTextDelta ?? "").length;
|
|
85
|
+
if (!len)
|
|
86
|
+
return state;
|
|
87
|
+
return {
|
|
88
|
+
...state,
|
|
89
|
+
items: updateStep(state.items, chunk.toolCallId, (s) => {
|
|
90
|
+
const chars = (s.streamedChars ?? 0) + len;
|
|
91
|
+
return { ...s, streamedChars: chars, detail: formatStreamSize(chars) };
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
76
95
|
case "tool-output-available":
|
|
77
|
-
|
|
96
|
+
// Settled — drop the live streaming size; the row is just label + done.
|
|
97
|
+
return { ...state, items: updateStep(state.items, chunk.toolCallId, (s) => ({ ...s, status: "done", output: chunk.output, detail: undefined, streamedChars: undefined })) };
|
|
78
98
|
case "tool-output-error":
|
|
79
99
|
return { ...state, items: updateStep(state.items, chunk.toolCallId, (s) => ({ ...s, status: "error", errorText: chunk.errorText ?? "The tool failed." })) };
|
|
80
100
|
case "error":
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface AskAiArgs {
|
|
2
|
+
/** Prefills the chat composer — the user sees, edits, and sends it
|
|
3
|
+
* themselves. Nothing runs until they do. */
|
|
4
|
+
prompt?: string;
|
|
5
|
+
/** Files to attach to the chat (by id, from `readFiles(cell)`). The host
|
|
6
|
+
* resolves each id itself and opens the file preview beside the chat. */
|
|
7
|
+
file_ids?: string[];
|
|
8
|
+
/** Records the chat should know about (ids from query rows). The host
|
|
9
|
+
* resolves them to table context — the agent can then read and act on
|
|
10
|
+
* them under the signed-in user's authority. */
|
|
11
|
+
record_ids?: string[];
|
|
12
|
+
/** Free-text grounding for the agent ("Shipment SGN-2481, customer …").
|
|
13
|
+
* The host stamps the app's identity alongside it automatically. */
|
|
14
|
+
context?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Hand off to the Lotics chat agent — opens the messenger on a FRESH chat
|
|
18
|
+
* seeded with the given files, records, and prompt.
|
|
19
|
+
*
|
|
20
|
+
* Use this for dialogue-shaped work: iterating on a document ("edit this
|
|
21
|
+
* invoice"), drafting from record context, open-ended questions. For
|
|
22
|
+
* structured judgment that commits back into the app's own data
|
|
23
|
+
* (extract/check/match/rank), use `useAgentRun` with a review surface
|
|
24
|
+
* instead — the outcome of `askAi` lands in chat, not in your workflows.
|
|
25
|
+
*
|
|
26
|
+
* The user stays in control: the prompt is only prefilled, attachments are
|
|
27
|
+
* visible in the composer, and nothing is sent until they press send.
|
|
28
|
+
*
|
|
29
|
+
* ```tsx
|
|
30
|
+
* import { askAi } from "@lotics/app-sdk";
|
|
31
|
+
* await askAi({
|
|
32
|
+
* file_ids: [file.id],
|
|
33
|
+
* record_ids: [row.id],
|
|
34
|
+
* prompt: "Update the header of this invoice to match our letterhead.",
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Only available embedded in Lotics — rejects in standalone/dev mode.
|
|
39
|
+
*/
|
|
40
|
+
export declare function askAi(args: AskAiArgs): Promise<void>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { rpc } from "./rpc.js";
|
|
2
|
+
/**
|
|
3
|
+
* Hand off to the Lotics chat agent — opens the messenger on a FRESH chat
|
|
4
|
+
* seeded with the given files, records, and prompt.
|
|
5
|
+
*
|
|
6
|
+
* Use this for dialogue-shaped work: iterating on a document ("edit this
|
|
7
|
+
* invoice"), drafting from record context, open-ended questions. For
|
|
8
|
+
* structured judgment that commits back into the app's own data
|
|
9
|
+
* (extract/check/match/rank), use `useAgentRun` with a review surface
|
|
10
|
+
* instead — the outcome of `askAi` lands in chat, not in your workflows.
|
|
11
|
+
*
|
|
12
|
+
* The user stays in control: the prompt is only prefilled, attachments are
|
|
13
|
+
* visible in the composer, and nothing is sent until they press send.
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* import { askAi } from "@lotics/app-sdk";
|
|
17
|
+
* await askAi({
|
|
18
|
+
* file_ids: [file.id],
|
|
19
|
+
* record_ids: [row.id],
|
|
20
|
+
* prompt: "Update the header of this invoice to match our letterhead.",
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Only available embedded in Lotics — rejects in standalone/dev mode.
|
|
25
|
+
*/
|
|
26
|
+
export function askAi(args) {
|
|
27
|
+
const hasPayload = (args.prompt ?? "") !== "" ||
|
|
28
|
+
(args.file_ids?.length ?? 0) > 0 ||
|
|
29
|
+
(args.record_ids?.length ?? 0) > 0 ||
|
|
30
|
+
(args.context ?? "") !== "";
|
|
31
|
+
if (!hasPayload) {
|
|
32
|
+
return Promise.reject(new Error("askAi requires at least one of prompt, file_ids, record_ids, context"));
|
|
33
|
+
}
|
|
34
|
+
return rpc("askAi", args);
|
|
35
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export type { GeofenceZone, GeoCoords, GeofenceOutcome, GeofenceOptions } from "
|
|
|
26
26
|
export { rpc, isEmbedded } from "./rpc.js";
|
|
27
27
|
export type { RpcOp } from "./rpc.js";
|
|
28
28
|
export { openExternal } from "./open_external.js";
|
|
29
|
+
export { askAi, type AskAiArgs } from "./ask_ai.js";
|
|
29
30
|
export { downloadFile } from "./download.js";
|
|
30
31
|
export { readMembers } from "./members.js";
|
|
31
32
|
export type { ResolvedMember } from "./members.js";
|
package/dist/src/index.js
CHANGED
|
@@ -21,6 +21,7 @@ export { useViewer } from "./viewer.js";
|
|
|
21
21
|
export { requestGeofencedLocation, isWithinZone } from "./geolocation.js";
|
|
22
22
|
export { rpc, isEmbedded } from "./rpc.js";
|
|
23
23
|
export { openExternal } from "./open_external.js";
|
|
24
|
+
export { askAi } from "./ask_ai.js";
|
|
24
25
|
export { downloadFile } from "./download.js";
|
|
25
26
|
export { readMembers } from "./members.js";
|
|
26
27
|
export { readSelect } from "./select.js";
|
package/dist/src/router.d.ts
CHANGED
package/dist/src/rpc.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { type UrlParams, type UrlParamsPatch } from "./url_params.js";
|
|
|
19
19
|
* app → host: { id, op, payload }
|
|
20
20
|
* host → app: { id, type: "result", data } | { id, type: "error", message }
|
|
21
21
|
*/
|
|
22
|
-
export type RpcOp = "query" | "field_options" | "workflow" | "agentRuns" | "agentRun.get" | "agentRun.cancel" | "upload" | "members" | "context" | "openExternal" | "urlState.get" | "urlState.set" | "comments.list" | "comments.create" | "comments.update" | "comments.delete" | "comments.counts";
|
|
22
|
+
export type RpcOp = "query" | "field_options" | "workflow" | "agentRuns" | "agentRun.get" | "agentRun.cancel" | "upload" | "members" | "context" | "openExternal" | "askAi" | "urlState.get" | "urlState.set" | "comments.list" | "comments.create" | "comments.update" | "comments.delete" | "comments.counts";
|
|
23
23
|
/** Payload for starting a streaming agent run. */
|
|
24
24
|
export interface AgentRunPayload {
|
|
25
25
|
alias: string;
|
package/dist/src/rpc.js
CHANGED
|
@@ -427,6 +427,10 @@ function rpcStandalone(op, payload) {
|
|
|
427
427
|
return standaloneContext();
|
|
428
428
|
case "openExternal":
|
|
429
429
|
return standaloneOpenExternal(payload);
|
|
430
|
+
case "askAi":
|
|
431
|
+
// The chat surface lives in the Lotics host — a standalone page has
|
|
432
|
+
// nowhere to hand off to.
|
|
433
|
+
return Promise.reject(new Error("askAi is only available when the app runs inside Lotics"));
|
|
430
434
|
case "urlState.get":
|
|
431
435
|
// Standalone is a top-level page — its own query string IS the store.
|
|
432
436
|
return Promise.resolve(parseSearch(window.location.search));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotics/app-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "Runtime SDK for Lotics custom-code apps — typed hooks, postMessage bridge, mount entry point",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@types/react": "^19.
|
|
42
|
+
"@types/react": "^19.2.17",
|
|
43
43
|
"@types/react-dom": "^19.0.0",
|
|
44
44
|
"react": "^19.2.0",
|
|
45
45
|
"react-dom": "^19.2.0",
|
|
46
|
-
"react-router-dom": "^7.
|
|
46
|
+
"react-router-dom": "^7.18.1"
|
|
47
47
|
},
|
|
48
48
|
"keywords": [
|
|
49
49
|
"lotics",
|
|
@@ -60,4 +60,4 @@
|
|
|
60
60
|
"url": "https://github.com/lotics/lotics.git",
|
|
61
61
|
"directory": "packages/app-sdk"
|
|
62
62
|
}
|
|
63
|
-
}
|
|
63
|
+
}
|