@cosmoops/connect-sdk 0.15.0 → 0.16.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/CHANGELOG.md +69 -0
- package/README.md +2 -1
- package/dist/cjs/client/components/ai-screens.js +73 -0
- package/dist/cjs/client/components/ai-widgets.js +37 -0
- package/dist/cjs/client/components/ask-ai.js +31 -5
- package/dist/cjs/client/index.js +5 -2
- package/dist/cjs/index.js +6 -1
- package/dist/cjs/mcp/index.js +90 -0
- package/dist/cjs/mcp/tools.js +369 -0
- package/dist/cjs/server/client.js +1 -1
- package/dist/cjs/server/handler.js +7 -0
- package/dist/cjs/server/resources/ai.js +8 -1
- package/dist/cjs/store-schema.js +66 -0
- package/dist/esm/client/components/ai-screens.js +69 -0
- package/dist/esm/client/components/ai-widgets.js +37 -1
- package/dist/esm/client/components/ask-ai.js +32 -6
- package/dist/esm/client/index.js +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/mcp/index.js +85 -0
- package/dist/esm/mcp/tools.js +366 -0
- package/dist/esm/server/client.js +1 -1
- package/dist/esm/server/handler.js +7 -0
- package/dist/esm/server/resources/ai.js +8 -1
- package/dist/esm/store-schema.js +62 -0
- package/dist/types/client/components/ai-screens.d.ts +25 -0
- package/dist/types/client/components/ai-widgets.d.ts +34 -1
- package/dist/types/client/components/ask-ai.d.ts +47 -1
- package/dist/types/client/index.d.ts +2 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/mcp/index.d.ts +74 -0
- package/dist/types/mcp/tools.d.ts +107 -0
- package/dist/types/server/client.d.ts +1 -1
- package/dist/types/server/resources/ai.d.ts +11 -1
- package/dist/types/store-schema.d.ts +52 -0
- package/dist/types/types.d.ts +50 -1
- package/package.json +10 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { storeIndexFor, storeIndexSignature } from "./store.js";
|
|
2
|
+
/**
|
|
3
|
+
* The signature of an index scanned backwards.
|
|
4
|
+
*
|
|
5
|
+
* Firestore serves a query from an index in either direction as long as *every*
|
|
6
|
+
* ordered field is flipped, so `[a asc, b desc]` also answers `[a desc, b asc]`.
|
|
7
|
+
* Mirrored by Connect's own `indexCovers`; kept in step with it deliberately,
|
|
8
|
+
* because a check here that is stricter than the server's would refuse queries
|
|
9
|
+
* that actually work, and a looser one would promise queries that do not.
|
|
10
|
+
*/
|
|
11
|
+
function reversedIndexSignature(fields) {
|
|
12
|
+
return storeIndexSignature(fields.map((entry) => entry.direction === "array-contains"
|
|
13
|
+
? entry
|
|
14
|
+
: { field: entry.field, direction: entry.direction === "asc" ? "desc" : "asc" }));
|
|
15
|
+
}
|
|
16
|
+
/** Whether one of `declared` can serve a query needing exactly `required`. */
|
|
17
|
+
export function storeIndexCovers(declared, required) {
|
|
18
|
+
const wanted = storeIndexSignature(required);
|
|
19
|
+
const mirrored = reversedIndexSignature(required);
|
|
20
|
+
return declared.some((index) => {
|
|
21
|
+
const signature = storeIndexSignature(index.fields);
|
|
22
|
+
return signature === wanted || signature === mirrored;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Why a collection cannot serve a query, in words a caller can act on - or
|
|
27
|
+
* `null` when it can.
|
|
28
|
+
*
|
|
29
|
+
* Runs the same two rules Connect runs, in the same order: every field must be
|
|
30
|
+
* declared queryable, and the shape must be inside an automatic single-field
|
|
31
|
+
* index or covered by a declared composite one. Checking here rather than
|
|
32
|
+
* letting the call fail is not about saving a round trip - it is so the refusal
|
|
33
|
+
* can name the index to declare while the caller still has the query in hand.
|
|
34
|
+
*/
|
|
35
|
+
export function describeQueryRejection(collection, query) {
|
|
36
|
+
const filters = query.filters ?? [];
|
|
37
|
+
const orderBy = query.orderBy ?? null;
|
|
38
|
+
const queryable = new Set(collection.queryableFields);
|
|
39
|
+
const undeclared = [
|
|
40
|
+
...new Set([...filters.map((filter) => filter.field), ...(orderBy ? [orderBy.field] : [])]),
|
|
41
|
+
].filter((field) => !queryable.has(field));
|
|
42
|
+
if (undeclared.length > 0) {
|
|
43
|
+
return `"${collection.name}" cannot be filtered or ordered on ${undeclared.join(", ")}. Queryable fields are: ${collection.queryableFields.join(", ")}.`;
|
|
44
|
+
}
|
|
45
|
+
let required;
|
|
46
|
+
try {
|
|
47
|
+
required = storeIndexFor({ filters, orderBy });
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
// Firestore cannot serve the shape at all - two `array-contains`, ranges
|
|
51
|
+
// over two fields, a range ordered by another field. No index would help,
|
|
52
|
+
// so the thrown message is the whole answer.
|
|
53
|
+
return error instanceof Error ? error.message : String(error);
|
|
54
|
+
}
|
|
55
|
+
if (!required)
|
|
56
|
+
return null;
|
|
57
|
+
if (storeIndexCovers(collection.indexes, required))
|
|
58
|
+
return null;
|
|
59
|
+
return `"${collection.name}" has no index for this query. It would need one over ${required
|
|
60
|
+
.map((entry) => `${entry.field} (${entry.direction})`)
|
|
61
|
+
.join(", ")}.`;
|
|
62
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ConnectScreen } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The declared screen a live path is on, or `null` if none matches.
|
|
4
|
+
*
|
|
5
|
+
* `null` is a normal outcome, not an error: an application need only describe
|
|
6
|
+
* the screens it wants the assistant to know about, and an undescribed one
|
|
7
|
+
* simply costs the assistant its orientation rather than costing the user an
|
|
8
|
+
* answer.
|
|
9
|
+
*/
|
|
10
|
+
export declare function matchConnectScreen(screens: readonly ConnectScreen[], path: string): ConnectScreen | null;
|
|
11
|
+
/**
|
|
12
|
+
* Whether an href the model produced may be rendered as a link.
|
|
13
|
+
*
|
|
14
|
+
* Same reasoning as Connect's own `showActions` check, and the same conclusion:
|
|
15
|
+
* an assistant that can emit an arbitrary URL is a redirect the model can be
|
|
16
|
+
* talked into, and retrieved content shares a context window with its
|
|
17
|
+
* tool-calling authority. So a link is drawn only when it resolves to a screen
|
|
18
|
+
* *your application declared* - which makes an external destination structurally
|
|
19
|
+
* impossible, since no route pattern has a scheme or a host.
|
|
20
|
+
*
|
|
21
|
+
* The protocol-relative form is refused separately: `//evil.example.com` has no
|
|
22
|
+
* scheme for a path matcher to trip over, and would otherwise reach an anchor
|
|
23
|
+
* as an absolute destination.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isLinkableConnectScreen(screens: readonly ConnectScreen[], href: string): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { AiChartInput, AiDetailsInput, AiMetricsInput, AiSourcesInput, AiStepsInput, AiTableInput, AiTimelineInput } from "../../types.js";
|
|
2
|
+
import type { AiChartInput, AiDetailsInput, AiMetricsInput, AiSourcesInput, AiStepsInput, AiActionsInput, AiTableInput, AiTimelineInput, ConnectScreen } from "../../types.js";
|
|
3
3
|
/**
|
|
4
4
|
* `showTable`. Sorting and CSV are what a table is *for* - once an answer is
|
|
5
5
|
* tabular the next thing anyone wants is to reorder it or take it elsewhere,
|
|
@@ -40,3 +40,36 @@ export declare function AiSteps({ title, steps, renderDetail, }: Readonly<AiStep
|
|
|
40
40
|
* without is still a named row - never a marker with nothing behind it.
|
|
41
41
|
*/
|
|
42
42
|
export declare function AiSources({ sources }: Readonly<AiSourcesInput>): import("react").JSX.Element;
|
|
43
|
+
/**
|
|
44
|
+
* Where to go next, as links into *your* application.
|
|
45
|
+
*
|
|
46
|
+
* ## Why links and not navigation
|
|
47
|
+
*
|
|
48
|
+
* The obvious version of "the assistant can act" moves the user. It is also the
|
|
49
|
+
* version that breaks the property the whole design rests on: nothing happens
|
|
50
|
+
* without a click. Retrieved content shares a context window with the model's
|
|
51
|
+
* tool-calling authority, so an auto-navigate is a redirect primitive that
|
|
52
|
+
* content can reach. A link cannot be, and loses nothing - the user was going
|
|
53
|
+
* to click something anyway.
|
|
54
|
+
*
|
|
55
|
+
* ## Why every href is checked against your declared screens
|
|
56
|
+
*
|
|
57
|
+
* The model can emit any string. Only your application knows which strings are
|
|
58
|
+
* real routes, which is why `screens` is a prop rather than something Connect
|
|
59
|
+
* could supply. A link that does not resolve is *dropped* rather than rendered
|
|
60
|
+
* broken - the prose around it still stands, so a missing link reads as terse
|
|
61
|
+
* rather than wrong.
|
|
62
|
+
*
|
|
63
|
+
* `renderLink` is how this stays router-agnostic. Given one, a link is your
|
|
64
|
+
* framework's - a Next `<Link>`, a TanStack `<Link>` - and navigation is a
|
|
65
|
+
* client transition. Without one it falls back to an anchor, which works
|
|
66
|
+
* everywhere and costs a document load.
|
|
67
|
+
*/
|
|
68
|
+
export declare function AiActions({ title, actions, screens, renderLink, }: Readonly<AiActionsInput & {
|
|
69
|
+
screens: readonly ConnectScreen[];
|
|
70
|
+
renderLink?: (props: {
|
|
71
|
+
href: string;
|
|
72
|
+
className: string;
|
|
73
|
+
children: ReactNode;
|
|
74
|
+
}) => ReactNode;
|
|
75
|
+
}>): import("react").JSX.Element | null;
|
|
@@ -1,5 +1,51 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import { type StreamdownProps } from "streamdown";
|
|
3
|
+
import type { ConnectScreen } from "../../types.js";
|
|
2
4
|
export interface ConnectAskAiProps {
|
|
5
|
+
/**
|
|
6
|
+
* The screens in your application, described for the assistant.
|
|
7
|
+
*
|
|
8
|
+
* Without these it answers from general knowledge about software, because
|
|
9
|
+
* Connect has no idea what your product is - see `ConnectScreen`. With them,
|
|
10
|
+
* "what can I do here?" answers in your own words, the empty panel offers
|
|
11
|
+
* your own opening questions, and `showActions` can link into your routes.
|
|
12
|
+
*
|
|
13
|
+
* Prose and links only: Connect never uses them to decide what anyone may do,
|
|
14
|
+
* and never executes anything on your behalf.
|
|
15
|
+
*/
|
|
16
|
+
screens?: readonly ConnectScreen[];
|
|
17
|
+
/**
|
|
18
|
+
* The route the user is currently on.
|
|
19
|
+
*
|
|
20
|
+
* **Usually omit this.** Left unset, the panel resolves the live path itself
|
|
21
|
+
* through `useActivePath`, which reads `location.pathname` from a
|
|
22
|
+
* `useSyncExternalStore` whose server snapshot is empty - so it works inside
|
|
23
|
+
* a shared App Shell, where `usePathname()` is refused outright
|
|
24
|
+
* (`CLIENT_HOOK_DYNAMIC`) because URL data cannot live in an artifact shared
|
|
25
|
+
* across URLs. That is the same reason `ConnectAppShell` recommends leaving
|
|
26
|
+
* its own `activePath` unset in a Next app.
|
|
27
|
+
*
|
|
28
|
+
* Pass it only where your router's path is not `location.pathname` - a
|
|
29
|
+
* hash router, or a panel embedded under a basePath your routes do not
|
|
30
|
+
* include.
|
|
31
|
+
*/
|
|
32
|
+
activePath?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Renders an action link with your router, so following one is a client
|
|
35
|
+
* transition rather than a document load. Omit it and links are plain
|
|
36
|
+
* anchors, which work everywhere.
|
|
37
|
+
*
|
|
38
|
+
* ```tsx
|
|
39
|
+
* renderLink={({ href, className, children }) => (
|
|
40
|
+
* <Link href={href} className={className}>{children}</Link>
|
|
41
|
+
* )}
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
renderLink?: (props: {
|
|
45
|
+
href: string;
|
|
46
|
+
className: string;
|
|
47
|
+
children: ReactNode;
|
|
48
|
+
}) => ReactNode;
|
|
3
49
|
/** Panel heading. Defaults to `"Ask AI"`. */
|
|
4
50
|
title?: string;
|
|
5
51
|
/** Byline over each assistant turn. Defaults to `"Assistant"`. */
|
|
@@ -70,4 +116,4 @@ export interface ConnectAskAiProps {
|
|
|
70
116
|
* Neither is something the browser can discover, so both surface as a message
|
|
71
117
|
* in the panel rather than as a hidden button.
|
|
72
118
|
*/
|
|
73
|
-
export declare function ConnectAskAi({ title, assistantName, placeholder, hint, defaultOpen, markdownPlugins, }: ConnectAskAiProps): import("react").JSX.Element | null;
|
|
119
|
+
export declare function ConnectAskAi({ title, assistantName, placeholder, hint, defaultOpen, markdownPlugins, screens, activePath, renderLink, }: ConnectAskAiProps): import("react").JSX.Element | null;
|
|
@@ -41,6 +41,8 @@ export type { ConnectSidebarNavProps, ConnectSideNavProps } from "./components/s
|
|
|
41
41
|
export { useActivePath } from "./use-active-path.js";
|
|
42
42
|
export { normalizeNav, flattenNav, normalizePath, resolveActivePath, isNavItemActive, gateExemptPaths, filterNavForAudience, } from "./components/nav.js";
|
|
43
43
|
export type { ConnectNav, ConnectNavItem, ConnectNavSection, ConnectNavLinkProps, ConnectLinkRenderer, } from "./components/nav.js";
|
|
44
|
+
export type { ConnectScreen } from "../types.js";
|
|
45
|
+
export { matchConnectScreen, isLinkableConnectScreen, } from "./components/ai-screens.js";
|
|
44
46
|
export { ConnectAppsGrid } from "./components/apps-grid.js";
|
|
45
47
|
export type { ConnectAppsGridProps } from "./components/apps-grid.js";
|
|
46
48
|
export { ConnectAskAi } from "./components/ask-ai.js";
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
* Holds the API key. Never import this from browser code.
|
|
11
11
|
* - `@cosmoops/connect-sdk/client` - React components and hooks. Talks to your
|
|
12
12
|
* own origin only.
|
|
13
|
+
* - `@cosmoops/connect-sdk/mcp` - your store's declaration, derived into MCP
|
|
14
|
+
* tools. Holds the API key; never import this from browser code.
|
|
13
15
|
*/
|
|
14
16
|
export { ConnectError, isConnectError } from "./errors.js";
|
|
15
17
|
export { blockingBlockers, normalizePlanFeatures } from "./helpers.js";
|
|
16
18
|
export { arrayRemove, arrayUnion, deleteField, documentExists, increment, serverTimestamp, storeIndexFor, storeIndexSignature, unchangedSince, } from "./store.js";
|
|
17
19
|
export type { StoreIndexDirection, StoreIndexField } from "./store.js";
|
|
20
|
+
export { describeQueryRejection, storeIndexCovers } from "./store-schema.js";
|
|
21
|
+
export type { StoreCollectionDeclaration } from "./store-schema.js";
|
|
18
22
|
export type { WirePlanFeature } from "./helpers.js";
|
|
19
23
|
export type * from "./types.js";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@cosmoops/connect-sdk/mcp` - your application's store, as an MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Server-only: every call goes out through `ConnectClient`, which holds the API
|
|
5
|
+
* key. The `browser` export condition resolves this entry to a stub, so an
|
|
6
|
+
* import from client code fails the build rather than shipping the key.
|
|
7
|
+
*
|
|
8
|
+
* ## Zero dependencies, on purpose
|
|
9
|
+
*
|
|
10
|
+
* This entry does not import `@modelcontextprotocol/sdk`, and the package takes
|
|
11
|
+
* no dependency on it. `listTools()` and `callTool()` answer in the shapes the
|
|
12
|
+
* MCP protocol defines, so wiring them to the official server is four lines and
|
|
13
|
+
* a transport you choose:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* const toolset = createStoreToolset({
|
|
17
|
+
* collections: STORE_SCHEMA,
|
|
18
|
+
* data: connect.data,
|
|
19
|
+
* workspaceId: session.workspaceId,
|
|
20
|
+
* asUser: session.sessionToken,
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* server.setRequestHandler(ListToolsRequestSchema, () => toolset.listTools())
|
|
24
|
+
* server.setRequestHandler(CallToolRequestSchema, (request) =>
|
|
25
|
+
* toolset.callTool(request.params.name, request.params.arguments ?? {}),
|
|
26
|
+
* )
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* The alternative - depending on the MCP SDK so this file could return an
|
|
30
|
+
* `McpServer` - would put a version of it in every install of this package,
|
|
31
|
+
* including the ones that never touch MCP, and pin partners to whichever
|
|
32
|
+
* version shipped here. The protocol shapes are stable and small; the coupling
|
|
33
|
+
* would not be.
|
|
34
|
+
*
|
|
35
|
+
* ## One toolset per session
|
|
36
|
+
*
|
|
37
|
+
* `asUser` is an end user's Connect session token, and Connect checks each
|
|
38
|
+
* collection's declared permission against that member's live roles. A toolset
|
|
39
|
+
* is therefore scoped to one signed-in user - build it per request from
|
|
40
|
+
* `getConnectSession()`, or pass a function for `asUser` if the surrounding
|
|
41
|
+
* server outlives a session.
|
|
42
|
+
*/
|
|
43
|
+
export { deriveStoreTools } from "./tools.js";
|
|
44
|
+
export type { JsonSchema, StoreDataPort, StoreTool, StoreToolWrites, StoreToolsetOptions, } from "./tools.js";
|
|
45
|
+
import { type StoreTool, type StoreToolsetOptions } from "./tools.js";
|
|
46
|
+
/** One entry of an MCP `tools/list` response. */
|
|
47
|
+
export interface McpToolDescriptor {
|
|
48
|
+
name: string;
|
|
49
|
+
title: string;
|
|
50
|
+
description: string;
|
|
51
|
+
inputSchema: Record<string, unknown>;
|
|
52
|
+
annotations: {
|
|
53
|
+
readOnlyHint: boolean;
|
|
54
|
+
destructiveHint: boolean;
|
|
55
|
+
idempotentHint: boolean;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** An MCP `tools/call` result. */
|
|
59
|
+
export interface McpToolResult {
|
|
60
|
+
content: {
|
|
61
|
+
type: "text";
|
|
62
|
+
text: string;
|
|
63
|
+
}[];
|
|
64
|
+
isError?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface StoreToolset {
|
|
67
|
+
/** The derived tools, if you would rather register them yourself. */
|
|
68
|
+
readonly tools: readonly StoreTool[];
|
|
69
|
+
listTools(): {
|
|
70
|
+
tools: McpToolDescriptor[];
|
|
71
|
+
};
|
|
72
|
+
callTool(name: string, args: Record<string, unknown>): Promise<McpToolResult>;
|
|
73
|
+
}
|
|
74
|
+
export declare function createStoreToolset(options: StoreToolsetOptions): StoreToolset;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { StoreAggregation, StoreDocument, StoreOrderBy, StoreQueryFilter } from "../types.js";
|
|
2
|
+
import { type StoreCollectionDeclaration } from "../store-schema.js";
|
|
3
|
+
/** A JSON Schema object, as the MCP `tools/list` response carries it. */
|
|
4
|
+
export type JsonSchema = Record<string, unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* One derived tool: the wire definition, and the call behind it.
|
|
7
|
+
*
|
|
8
|
+
* Shaped so `listTools`/`callTool` can be answered without this module knowing
|
|
9
|
+
* what transport is in front of it - see the note on `createStoreToolset`.
|
|
10
|
+
*/
|
|
11
|
+
export interface StoreTool {
|
|
12
|
+
name: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
inputSchema: JsonSchema;
|
|
16
|
+
annotations: {
|
|
17
|
+
readOnlyHint: boolean;
|
|
18
|
+
destructiveHint: boolean;
|
|
19
|
+
idempotentHint: boolean;
|
|
20
|
+
};
|
|
21
|
+
execute(args: Record<string, unknown>): Promise<unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The subset of `ConnectClient["data"]` a toolset uses.
|
|
25
|
+
*
|
|
26
|
+
* Structural rather than the concrete class so this module pulls in neither the
|
|
27
|
+
* transport nor the key-holding client, and so a test can pass a recording
|
|
28
|
+
* double without a network seam of its own.
|
|
29
|
+
*/
|
|
30
|
+
export interface StoreDataPort {
|
|
31
|
+
get(input: {
|
|
32
|
+
workspaceId?: string;
|
|
33
|
+
collection: string;
|
|
34
|
+
id: string;
|
|
35
|
+
asUser?: string;
|
|
36
|
+
}): Promise<StoreDocument>;
|
|
37
|
+
query(input: {
|
|
38
|
+
workspaceId?: string;
|
|
39
|
+
collection: string;
|
|
40
|
+
filters?: StoreQueryFilter[];
|
|
41
|
+
orderBy?: StoreOrderBy;
|
|
42
|
+
limit?: number;
|
|
43
|
+
cursor?: string | null;
|
|
44
|
+
asUser?: string;
|
|
45
|
+
}): Promise<{
|
|
46
|
+
items: StoreDocument[];
|
|
47
|
+
nextCursor: string | null;
|
|
48
|
+
}>;
|
|
49
|
+
aggregate(input: {
|
|
50
|
+
workspaceId?: string;
|
|
51
|
+
collection: string;
|
|
52
|
+
aggregations: StoreAggregation[];
|
|
53
|
+
filters?: StoreQueryFilter[];
|
|
54
|
+
asUser?: string;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
values: Record<string, number | null>;
|
|
57
|
+
}>;
|
|
58
|
+
create(input: {
|
|
59
|
+
workspaceId?: string;
|
|
60
|
+
collection: string;
|
|
61
|
+
data: Record<string, unknown>;
|
|
62
|
+
id?: string;
|
|
63
|
+
asUser?: string;
|
|
64
|
+
}): Promise<StoreDocument>;
|
|
65
|
+
update(input: {
|
|
66
|
+
workspaceId?: string;
|
|
67
|
+
collection: string;
|
|
68
|
+
id: string;
|
|
69
|
+
data: Record<string, unknown>;
|
|
70
|
+
asUser?: string;
|
|
71
|
+
}): Promise<StoreDocument>;
|
|
72
|
+
delete(input: {
|
|
73
|
+
workspaceId?: string;
|
|
74
|
+
collection: string;
|
|
75
|
+
id: string;
|
|
76
|
+
asUser?: string;
|
|
77
|
+
}): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* How much of the write surface to derive.
|
|
81
|
+
*
|
|
82
|
+
* Defaults to `"none"`. A toolset is usually handed to a model driving somebody
|
|
83
|
+
* else's conversation, and the blast radius of a derived `delete` on every
|
|
84
|
+
* collection is not something to acquire by default - opting in is one word,
|
|
85
|
+
* and discovering it by accident is not recoverable.
|
|
86
|
+
*/
|
|
87
|
+
export type StoreToolWrites = "none" | "create" | "all";
|
|
88
|
+
export interface StoreToolsetOptions {
|
|
89
|
+
/** The application's own declaration - the same array seeded to `Application.storeSchema`. */
|
|
90
|
+
collections: readonly StoreCollectionDeclaration[];
|
|
91
|
+
data: StoreDataPort;
|
|
92
|
+
/**
|
|
93
|
+
* The end user's Connect session token. Every call is made on their behalf.
|
|
94
|
+
*
|
|
95
|
+
* A function is resolved per call, for a toolset outliving one session.
|
|
96
|
+
*/
|
|
97
|
+
asUser: string | (() => string | Promise<string>);
|
|
98
|
+
/** Omit for an application-scoped collection; required for a workspace-scoped one. */
|
|
99
|
+
workspaceId?: string;
|
|
100
|
+
writes?: StoreToolWrites;
|
|
101
|
+
/** Prepended to every tool name, for a server mounting more than one source. */
|
|
102
|
+
prefix?: string;
|
|
103
|
+
/** Collections to derive from. Defaults to every declared `data` collection. */
|
|
104
|
+
include?: readonly string[];
|
|
105
|
+
}
|
|
106
|
+
/** Every tool one declaration yields, in a stable order. */
|
|
107
|
+
export declare function deriveStoreTools(options: StoreToolsetOptions): StoreTool[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TransportOptions } from "../http.js";
|
|
2
|
-
import type { ChatMessage } from "../../types.js";
|
|
2
|
+
import type { ChatMessage, ConnectScreen } from "../../types.js";
|
|
3
3
|
/**
|
|
4
4
|
* RAG chat and structured extraction against a workspace's data.
|
|
5
5
|
*
|
|
@@ -21,6 +21,16 @@ export declare class AiResource {
|
|
|
21
21
|
workspaceId: string;
|
|
22
22
|
messages: ChatMessage[];
|
|
23
23
|
asUser?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Your application's screens, so the assistant answers in your product's
|
|
26
|
+
* words and can link into it - see `ConnectScreen`. Prose and links only:
|
|
27
|
+
* Connect never uses these to decide what anyone may do.
|
|
28
|
+
*/
|
|
29
|
+
screens?: readonly ConnectScreen[] | unknown;
|
|
30
|
+
/** Where the user is, as `{ pathname }`. Matched against `screens`. */
|
|
31
|
+
screenContext?: {
|
|
32
|
+
pathname?: string;
|
|
33
|
+
} | unknown;
|
|
24
34
|
}, options?: {
|
|
25
35
|
signal?: AbortSignal;
|
|
26
36
|
}): Promise<Response>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { StoreQueryFilter, StoreOrderBy } from "./types.js";
|
|
2
|
+
import { type StoreIndexField } from "./store.js";
|
|
3
|
+
/**
|
|
4
|
+
* One collection as an application declares it, and the helpers that read a
|
|
5
|
+
* declaration back.
|
|
6
|
+
*
|
|
7
|
+
* The declaration is the same shape Connect stores on `Application.storeSchema`
|
|
8
|
+
* and the same one `scripts/seed-*-application.ts` writes there, so an app that
|
|
9
|
+
* builds its schema from its own query matrix can hand the result straight to
|
|
10
|
+
* anything here without a second type of its own. Connect remains the
|
|
11
|
+
* authority - this is a local copy of what was declared, useful for deriving
|
|
12
|
+
* things *from* the declaration, never for deciding access.
|
|
13
|
+
*
|
|
14
|
+
* Isomorphic and dependency-free, like the rest of the root entry.
|
|
15
|
+
*/
|
|
16
|
+
export interface StoreCollectionDeclaration {
|
|
17
|
+
name: string;
|
|
18
|
+
kind: "data" | "kv";
|
|
19
|
+
scope: "workspace" | "application";
|
|
20
|
+
/** Prose. Connect shows it in the Console; `deriveStoreTools` gives it to a model. */
|
|
21
|
+
description: string;
|
|
22
|
+
/** The per-user permission Connect checks when a call carries `asUser`. `null` means any member. */
|
|
23
|
+
readPermission: string | null;
|
|
24
|
+
writePermission: string | null;
|
|
25
|
+
/** Whether `data.scan` may read this collection across every workspace. */
|
|
26
|
+
crossWorkspace: boolean;
|
|
27
|
+
/** Create-only. Connect refuses a replace, a merge or a delete for every caller. */
|
|
28
|
+
appendOnly: boolean;
|
|
29
|
+
/** Nothing may be filtered, ordered or aggregated on a field not listed. */
|
|
30
|
+
queryableFields: string[];
|
|
31
|
+
indexes: {
|
|
32
|
+
fields: StoreIndexField[];
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
35
|
+
/** Whether one of `declared` can serve a query needing exactly `required`. */
|
|
36
|
+
export declare function storeIndexCovers(declared: readonly {
|
|
37
|
+
fields: StoreIndexField[];
|
|
38
|
+
}[], required: readonly StoreIndexField[]): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Why a collection cannot serve a query, in words a caller can act on - or
|
|
41
|
+
* `null` when it can.
|
|
42
|
+
*
|
|
43
|
+
* Runs the same two rules Connect runs, in the same order: every field must be
|
|
44
|
+
* declared queryable, and the shape must be inside an automatic single-field
|
|
45
|
+
* index or covered by a declared composite one. Checking here rather than
|
|
46
|
+
* letting the call fail is not about saving a round trip - it is so the refusal
|
|
47
|
+
* can name the index to declare while the caller still has the query in hand.
|
|
48
|
+
*/
|
|
49
|
+
export declare function describeQueryRejection(collection: StoreCollectionDeclaration, query: {
|
|
50
|
+
filters?: readonly StoreQueryFilter[];
|
|
51
|
+
orderBy?: StoreOrderBy | null;
|
|
52
|
+
}): string | null;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -898,8 +898,57 @@ export interface AiSourcesInput {
|
|
|
898
898
|
snippet?: string;
|
|
899
899
|
}[];
|
|
900
900
|
}
|
|
901
|
+
export interface AiActionsInput {
|
|
902
|
+
title?: string;
|
|
903
|
+
actions: {
|
|
904
|
+
label: string;
|
|
905
|
+
href: string;
|
|
906
|
+
detail?: string;
|
|
907
|
+
}[];
|
|
908
|
+
}
|
|
901
909
|
/** The tools this client knows how to draw. Anything else in the stream is ignored. */
|
|
902
|
-
export type AiToolName = "showTable" | "showChart" | "showMetrics" | "showTimeline" | "showDetails" | "showSteps" | "showSources";
|
|
910
|
+
export type AiToolName = "showTable" | "showChart" | "showMetrics" | "showTimeline" | "showDetails" | "showSteps" | "showSources" | "showActions";
|
|
911
|
+
/**
|
|
912
|
+
* One screen in your application, described for the assistant.
|
|
913
|
+
*
|
|
914
|
+
* ## Why you declare these rather than Connect discovering them
|
|
915
|
+
*
|
|
916
|
+
* Connect has no idea what your app is. It cannot know that `/deals` is a
|
|
917
|
+
* pipeline, that a "lead" becomes a "contact", or that the thing your users
|
|
918
|
+
* call a quote lives under Settings. Without that, an assistant sitting inside
|
|
919
|
+
* your product answers from general knowledge about software - which is exactly
|
|
920
|
+
* how it reads.
|
|
921
|
+
*
|
|
922
|
+
* Declaring a screen is the cheapest possible fix: one line of prose per route,
|
|
923
|
+
* and questions like "what can I do here?" start answering in your product's
|
|
924
|
+
* own words.
|
|
925
|
+
*
|
|
926
|
+
* ## What Connect does and does not do with them
|
|
927
|
+
*
|
|
928
|
+
* They are **prose and links, and nothing else**. Connect puts your
|
|
929
|
+
* descriptions into the model's context so it can answer and so `showActions`
|
|
930
|
+
* can link to your screens. It never uses them to decide what anyone may do -
|
|
931
|
+
* your app's own authorization is untouched and unconsulted here - and it never
|
|
932
|
+
* executes anything on your behalf.
|
|
933
|
+
*
|
|
934
|
+
* They also arrive from a browser, so Connect treats them as untrusted text:
|
|
935
|
+
* bounded in count and length, and flattened to single lines before they reach
|
|
936
|
+
* a prompt.
|
|
937
|
+
*/
|
|
938
|
+
export interface ConnectScreen {
|
|
939
|
+
/**
|
|
940
|
+
* The route, as your router writes it. Dynamic segments in brackets -
|
|
941
|
+
* `/deals/[dealId]` - so a live path with a real id still matches.
|
|
942
|
+
*/
|
|
943
|
+
path: string;
|
|
944
|
+
/** One line: what a person does here, in your product's words. */
|
|
945
|
+
purpose: string;
|
|
946
|
+
/**
|
|
947
|
+
* Opening questions offered as chips on an empty conversation, phrased as
|
|
948
|
+
* your users would type them. Two or three; a fourth is a menu.
|
|
949
|
+
*/
|
|
950
|
+
suggestions?: string[];
|
|
951
|
+
}
|
|
903
952
|
/**
|
|
904
953
|
* One document from the `data` store.
|
|
905
954
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmoops/connect-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Official server-side SDK for the CosmoOps Connect platform API - Login with Connect, entitlements, billing and metering.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -31,6 +31,15 @@
|
|
|
31
31
|
"import": "./dist/esm/next/index.js",
|
|
32
32
|
"require": "./dist/cjs/next/index.js"
|
|
33
33
|
},
|
|
34
|
+
"./mcp": {
|
|
35
|
+
"types": "./dist/types/mcp/index.d.ts",
|
|
36
|
+
"browser": {
|
|
37
|
+
"import": "./dist/esm/this-entry-is-server-only.js",
|
|
38
|
+
"require": "./dist/cjs/this-entry-is-server-only.js"
|
|
39
|
+
},
|
|
40
|
+
"import": "./dist/esm/mcp/index.js",
|
|
41
|
+
"require": "./dist/cjs/mcp/index.js"
|
|
42
|
+
},
|
|
34
43
|
"./client": {
|
|
35
44
|
"types": "./dist/types/client/index.d.ts",
|
|
36
45
|
"import": "./dist/esm/client/index.js",
|