@cosmoops/connect-sdk 0.28.0 → 0.30.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 +117 -0
- package/dist/cjs/client/components/ask-ai-history.js +106 -0
- package/dist/cjs/client/components/ask-ai-turns.js +87 -0
- package/dist/cjs/client/components/ask-ai.js +94 -24
- package/dist/cjs/client/provider.js +2 -2
- package/dist/cjs/server/client.js +5 -1
- package/dist/cjs/server/handler.js +75 -0
- package/dist/cjs/server/resources/ai.js +55 -0
- package/dist/cjs/server/resources/conversations.js +70 -0
- package/dist/esm/client/components/ask-ai-history.js +103 -0
- package/dist/esm/client/components/ask-ai-turns.js +82 -0
- package/dist/esm/client/components/ask-ai.js +93 -23
- package/dist/esm/client/provider.js +2 -2
- package/dist/esm/server/client.js +5 -1
- package/dist/esm/server/handler.js +75 -0
- package/dist/esm/server/resources/ai.js +55 -0
- package/dist/esm/server/resources/conversations.js +66 -0
- package/dist/types/client/components/ask-ai-history.d.ts +25 -0
- package/dist/types/client/components/ask-ai-turns.d.ts +55 -0
- package/dist/types/client/provider.d.ts +8 -1
- package/dist/types/server/client.d.ts +4 -1
- package/dist/types/server/resources/ai.d.ts +49 -1
- package/dist/types/server/resources/conversations.d.ts +68 -0
- package/dist/types/types.d.ts +130 -0
- package/package.json +1 -1
|
@@ -30,6 +30,10 @@ export class AiResource {
|
|
|
30
30
|
json: {
|
|
31
31
|
workspaceId: input.workspaceId,
|
|
32
32
|
messages: input.messages,
|
|
33
|
+
// Omitted when absent, which is how a caller opts out of history: no
|
|
34
|
+
// id, nothing stored. Connect also stores nothing for a call carrying
|
|
35
|
+
// no `asUser`, because a conversation belongs to a person.
|
|
36
|
+
...(input.conversationId ? { conversationId: input.conversationId } : {}),
|
|
33
37
|
// Omitted entirely when absent, rather than sent as `undefined` - an
|
|
34
38
|
// application that describes no screens should send no field.
|
|
35
39
|
...(input.screens ? { screens: input.screens } : {}),
|
|
@@ -40,6 +44,57 @@ export class AiResource {
|
|
|
40
44
|
signal: options.signal,
|
|
41
45
|
});
|
|
42
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* The signed-in user's past conversations in your application, most recent
|
|
49
|
+
* first.
|
|
50
|
+
*
|
|
51
|
+
* `asUser` is required rather than optional here, unlike on `chat`: the rows
|
|
52
|
+
* are scoped to a person, and there is no application-wide view of them to
|
|
53
|
+
* fall back to. Connect reads the user and the workspace from the token.
|
|
54
|
+
*
|
|
55
|
+
* Cursor-paginated with no total - the list is ordered by last use, which
|
|
56
|
+
* moves while it is being read.
|
|
57
|
+
*/
|
|
58
|
+
listConversations(input) {
|
|
59
|
+
const query = new URLSearchParams();
|
|
60
|
+
if (input.limit !== undefined)
|
|
61
|
+
query.set("limit", String(input.limit));
|
|
62
|
+
if (input.cursor)
|
|
63
|
+
query.set("cursor", input.cursor);
|
|
64
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : "";
|
|
65
|
+
return request(this.transport, {
|
|
66
|
+
method: "GET",
|
|
67
|
+
path: `/ai/conversations${suffix}`,
|
|
68
|
+
headers: actingAs(input.asUser),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One stored transcript, oldest turn first.
|
|
73
|
+
*
|
|
74
|
+
* `turns[].parts` comes back exactly as the model produced it - prose,
|
|
75
|
+
* reasoning and tool calls in order - so it can be handed straight back to a
|
|
76
|
+
* chat UI. Not this user's, or not your application's, is a `404` either way.
|
|
77
|
+
*/
|
|
78
|
+
getConversation(input) {
|
|
79
|
+
return request(this.transport, {
|
|
80
|
+
method: "GET",
|
|
81
|
+
path: `/ai/conversations/${encodeURIComponent(input.conversationId)}`,
|
|
82
|
+
headers: actingAs(input.asUser),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Deletes a conversation and every turn beneath it.
|
|
87
|
+
*
|
|
88
|
+
* Idempotent - one already gone answers `{ deleted: true }`, so a retry after
|
|
89
|
+
* a dropped response does not read as a failure.
|
|
90
|
+
*/
|
|
91
|
+
deleteConversation(input) {
|
|
92
|
+
return request(this.transport, {
|
|
93
|
+
method: "DELETE",
|
|
94
|
+
path: `/ai/conversations/${encodeURIComponent(input.conversationId)}`,
|
|
95
|
+
headers: actingAs(input.asUser),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
43
98
|
/** Extract structured JSON from a file or image URL, against a platform extraction schema referenced by id. */
|
|
44
99
|
async extract(input) {
|
|
45
100
|
const result = await request(this.transport, {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { request } from "../http.js";
|
|
2
|
+
import { actingAs } from "./acting-as.js";
|
|
3
|
+
/**
|
|
4
|
+
* What a workspace's agents are saying in writing.
|
|
5
|
+
*
|
|
6
|
+
* **The sibling of `calls`, with one difference that decides how it is
|
|
7
|
+
* gated: these are the *workspace's* threads, not your application's.** A call
|
|
8
|
+
* arrived on a channel you hold, so `calls.list` is scoped to your
|
|
9
|
+
* application. A conversation lives in the workspace's own subtree and belongs
|
|
10
|
+
* to it the way its contact book does - so reading one needs a live
|
|
11
|
+
* subscription in that workspace, and, when the call comes from a browser
|
|
12
|
+
* through the bridge, the signed-in member's own `agent:read`.
|
|
13
|
+
*
|
|
14
|
+
* Read-only, and deliberately. Sending on a thread is not a missing verb:
|
|
15
|
+
* inside WhatsApp's 24-hour window a reply is the agent's to make, outside it
|
|
16
|
+
* only an approved template may be sent, and posting into a thread would mean
|
|
17
|
+
* saying which of those it was and what it means for the agent still
|
|
18
|
+
* answering.
|
|
19
|
+
*/
|
|
20
|
+
export class ConversationsResource {
|
|
21
|
+
transport;
|
|
22
|
+
constructor(transport) {
|
|
23
|
+
this.transport = transport;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The workspace's threads, newest activity first.
|
|
27
|
+
*
|
|
28
|
+
* **Page until `hasMore` is `false`; do not stop on an empty `items`.** An
|
|
29
|
+
* inbox grows without bound, so there is no total count to page against.
|
|
30
|
+
*/
|
|
31
|
+
list(input) {
|
|
32
|
+
return request(this.transport, {
|
|
33
|
+
method: "GET",
|
|
34
|
+
path: "/conversations",
|
|
35
|
+
query: {
|
|
36
|
+
workspaceId: input.workspaceId,
|
|
37
|
+
contactAddress: input.contactAddress,
|
|
38
|
+
status: input.status,
|
|
39
|
+
cursor: input.cursor,
|
|
40
|
+
limit: input.limit === undefined ? undefined : String(input.limit),
|
|
41
|
+
},
|
|
42
|
+
headers: actingAs(input.asUser),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* One thread and its recent messages, oldest first.
|
|
47
|
+
*
|
|
48
|
+
* **Capped, not paged** - you get the same window the agent itself sees when
|
|
49
|
+
* it composes a reply, so you are never shown less context than it used.
|
|
50
|
+
*
|
|
51
|
+
* Turns that produced no message are included and carry `failure`. Render
|
|
52
|
+
* them: a transcript that silently drops them reads as a customer being
|
|
53
|
+
* ignored, which is the opposite of what happened.
|
|
54
|
+
*
|
|
55
|
+
* Throws `ConnectError` with `code: "not_found"` for a thread this workspace
|
|
56
|
+
* does not hold.
|
|
57
|
+
*/
|
|
58
|
+
get(conversationId, options) {
|
|
59
|
+
return request(this.transport, {
|
|
60
|
+
method: "GET",
|
|
61
|
+
path: `/conversations/${encodeURIComponent(conversationId)}`,
|
|
62
|
+
query: { workspaceId: options.workspaceId },
|
|
63
|
+
headers: actingAs(options.asUser),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AiConversationDetail } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Past Ask AI conversations, for reopening or deleting.
|
|
4
|
+
*
|
|
5
|
+
* ## It fetches only while it is open
|
|
6
|
+
*
|
|
7
|
+
* `<ConnectAskAi>` is mounted for the whole application shell, so an
|
|
8
|
+
* unconditional read here would put a request on every page load to populate a
|
|
9
|
+
* list most people never open. It reads on open, and again whenever the panel
|
|
10
|
+
* has written a new conversation since - which is what `refreshKey` is for.
|
|
11
|
+
*
|
|
12
|
+
* ## Deletion confirms inline, not in a dialog
|
|
13
|
+
*
|
|
14
|
+
* Destructive actions confirm; this one does it at the weight it deserves.
|
|
15
|
+
* Losing a transcript is recoverable by asking again, not a cancelled
|
|
16
|
+
* subscription, and a modal over a 400px panel would be heavier than the thing
|
|
17
|
+
* it guards. The row swaps to "Delete? / Cancel" and stays there until
|
|
18
|
+
* answered.
|
|
19
|
+
*/
|
|
20
|
+
export declare function AskAiHistory({ refreshKey, activeConversationId, onOpenConversation, }: Readonly<{
|
|
21
|
+
/** Changes when the panel has written something, so the list re-reads. */
|
|
22
|
+
refreshKey: number;
|
|
23
|
+
activeConversationId: string;
|
|
24
|
+
onOpenConversation: (id: string, detail: AiConversationDetail) => void;
|
|
25
|
+
}>): import("react").JSX.Element;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AiConversationTurn } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* One piece of an assistant turn, in the order it arrived.
|
|
4
|
+
*
|
|
5
|
+
* Blocks rather than one text string, because an answer interleaves prose and
|
|
6
|
+
* drawings - a sentence, a table, the sentence that reads the table - and
|
|
7
|
+
* flattening it would silently reorder the model's own argument.
|
|
8
|
+
*/
|
|
9
|
+
export type AskAiBlock = {
|
|
10
|
+
kind: "text";
|
|
11
|
+
text: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "tool";
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
input: unknown | null;
|
|
17
|
+
};
|
|
18
|
+
export interface AskAiTurn {
|
|
19
|
+
id: string;
|
|
20
|
+
role: "user" | "assistant";
|
|
21
|
+
blocks: AskAiBlock[];
|
|
22
|
+
/** The model's streamed thoughts, if it sent any. */
|
|
23
|
+
reasoning: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Index of the final text block, or `-1`. A reverse loop rather than
|
|
27
|
+
* `findLastIndex`, which is ES2023 - this package targets ES2022 so its
|
|
28
|
+
* published output stays usable on the runtimes its `engines` field claims.
|
|
29
|
+
*/
|
|
30
|
+
export declare function lastTextBlockIndex(blocks: AskAiBlock[]): number;
|
|
31
|
+
/** Appends a text delta to the open text block, or opens one after a widget. */
|
|
32
|
+
export declare function appendText(blocks: AskAiBlock[], text: string): AskAiBlock[];
|
|
33
|
+
/**
|
|
34
|
+
* Rebuilds the panel's turns from a stored transcript.
|
|
35
|
+
*
|
|
36
|
+
* The inverse of what the stream reader does live, and it has to agree with it
|
|
37
|
+
* or a reopened conversation renders differently from the one the user just
|
|
38
|
+
* had. Two shapes, one source: the stream delivers `text-delta`,
|
|
39
|
+
* `reasoning-delta` and `tool-input-available` frames, and storage keeps the
|
|
40
|
+
* settled `UIMessage` parts those frames built - `{ type: "text" }`,
|
|
41
|
+
* `{ type: "reasoning" }`, and `{ type: "tool-<name>" }` carrying the input the
|
|
42
|
+
* widget was drawn from.
|
|
43
|
+
*
|
|
44
|
+
* Three rules, each of which is a thing that would otherwise render wrong:
|
|
45
|
+
*
|
|
46
|
+
* - **A tool part with no input is dropped, not restored.** `input: null` means
|
|
47
|
+
* "still arriving" to the renderer, which draws a skeleton - correct live,
|
|
48
|
+
* and a shimmer that never resolves on a transcript that finished days ago.
|
|
49
|
+
* - **Reasoning is hoisted per turn**, not left in place. It is rendered above
|
|
50
|
+
* the answer in a disclosure, which is where it sat while streaming.
|
|
51
|
+
* - **A part this build cannot place is skipped rather than guessed at.**
|
|
52
|
+
* `parts` is the AI SDK's own shape and gains kinds over time; an unknown one
|
|
53
|
+
* costs a widget, and inventing a rendering for it costs the whole turn.
|
|
54
|
+
*/
|
|
55
|
+
export declare function turnsFromStored(turns: readonly AiConversationTurn[]): AskAiTurn[];
|
|
@@ -99,8 +99,15 @@ export interface ConnectContextValue {
|
|
|
99
99
|
* `path` through `useConnectResource` still needs to call that hook's own
|
|
100
100
|
* `refetch()` afterward; clearing the cache entry alone does not re-render
|
|
101
101
|
* anything by itself.
|
|
102
|
+
*
|
|
103
|
+
* `method` widens this to the one other verb the bridge answers, `DELETE`,
|
|
104
|
+
* for the routes where removal is the whole operation and a body would be
|
|
105
|
+
* ceremony - Ask AI's history is the first. It stays a two-value union
|
|
106
|
+
* rather than `string`: everything else on this bridge is a POST, and a
|
|
107
|
+
* free-form verb here would be an invitation to invent REST semantics the
|
|
108
|
+
* bridge's exact-match route table does not have.
|
|
102
109
|
*/
|
|
103
|
-
mutateResource<T>(path: string, body?: unknown): Promise<T>;
|
|
110
|
+
mutateResource<T>(path: string, body?: unknown, method?: "POST" | "DELETE"): Promise<T>;
|
|
104
111
|
/**
|
|
105
112
|
* Drops one path's cached read so the next `fetchResource` for it goes to
|
|
106
113
|
* the bridge again.
|
|
@@ -9,6 +9,7 @@ import { SchedulingResource } from "./resources/scheduling.js";
|
|
|
9
9
|
import { ContactsResource } from "./resources/contacts.js";
|
|
10
10
|
import { ChannelsResource } from "./resources/channels.js";
|
|
11
11
|
import { CallsResource } from "./resources/calls.js";
|
|
12
|
+
import { ConversationsResource } from "./resources/conversations.js";
|
|
12
13
|
import { KvResource } from "./resources/kv.js";
|
|
13
14
|
import { BillingResource } from "./resources/billing.js";
|
|
14
15
|
import { DriveResource } from "./resources/drive.js";
|
|
@@ -88,6 +89,8 @@ export declare class ConnectClient {
|
|
|
88
89
|
readonly channels: ChannelsResource;
|
|
89
90
|
/** The calls that arrived on those channels, with their transcripts. */
|
|
90
91
|
readonly calls: CallsResource;
|
|
92
|
+
/** The message threads a subscribing workspace's agents are holding. */
|
|
93
|
+
readonly conversations: ConversationsResource;
|
|
91
94
|
/** RAG chat and structured extraction. */
|
|
92
95
|
readonly ai: AiResource;
|
|
93
96
|
readonly enrichment: EnrichmentResource;
|
|
@@ -123,4 +126,4 @@ export declare class ConnectClient {
|
|
|
123
126
|
constructor(options: ConnectClientOptions);
|
|
124
127
|
}
|
|
125
128
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
126
|
-
export declare const SDK_VERSION = "0.
|
|
129
|
+
export declare const SDK_VERSION = "0.30.0";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TransportOptions } from "../http.js";
|
|
2
|
-
import type { ChatMessage, ConnectScreen } from "../../types.js";
|
|
2
|
+
import type { AiConversationDetail, AiConversationList, ChatMessage, ConnectScreen } from "../../types.js";
|
|
3
3
|
/**
|
|
4
4
|
* RAG chat and structured extraction against a workspace's data.
|
|
5
5
|
*
|
|
@@ -21,6 +21,15 @@ export declare class AiResource {
|
|
|
21
21
|
workspaceId: string;
|
|
22
22
|
messages: ChatMessage[];
|
|
23
23
|
asUser?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Your own id for this conversation, sent with every turn of it.
|
|
26
|
+
*
|
|
27
|
+
* What makes the conversation *resumable*: Connect upserts against it and
|
|
28
|
+
* `conversations.list`/`get` read it back. Persisted only alongside
|
|
29
|
+
* `asUser` - a conversation belongs to a person, and a server-to-server
|
|
30
|
+
* call acts as nobody. Omit it to keep a turn out of history entirely.
|
|
31
|
+
*/
|
|
32
|
+
conversationId?: string;
|
|
24
33
|
/**
|
|
25
34
|
* Your application's screens, so the assistant answers in your product's
|
|
26
35
|
* words and can link into it - see `ConnectScreen`. Prose and links only:
|
|
@@ -34,6 +43,45 @@ export declare class AiResource {
|
|
|
34
43
|
}, options?: {
|
|
35
44
|
signal?: AbortSignal;
|
|
36
45
|
}): Promise<Response>;
|
|
46
|
+
/**
|
|
47
|
+
* The signed-in user's past conversations in your application, most recent
|
|
48
|
+
* first.
|
|
49
|
+
*
|
|
50
|
+
* `asUser` is required rather than optional here, unlike on `chat`: the rows
|
|
51
|
+
* are scoped to a person, and there is no application-wide view of them to
|
|
52
|
+
* fall back to. Connect reads the user and the workspace from the token.
|
|
53
|
+
*
|
|
54
|
+
* Cursor-paginated with no total - the list is ordered by last use, which
|
|
55
|
+
* moves while it is being read.
|
|
56
|
+
*/
|
|
57
|
+
listConversations(input: {
|
|
58
|
+
asUser: string;
|
|
59
|
+
limit?: number;
|
|
60
|
+
cursor?: string;
|
|
61
|
+
}): Promise<AiConversationList>;
|
|
62
|
+
/**
|
|
63
|
+
* One stored transcript, oldest turn first.
|
|
64
|
+
*
|
|
65
|
+
* `turns[].parts` comes back exactly as the model produced it - prose,
|
|
66
|
+
* reasoning and tool calls in order - so it can be handed straight back to a
|
|
67
|
+
* chat UI. Not this user's, or not your application's, is a `404` either way.
|
|
68
|
+
*/
|
|
69
|
+
getConversation(input: {
|
|
70
|
+
conversationId: string;
|
|
71
|
+
asUser: string;
|
|
72
|
+
}): Promise<AiConversationDetail>;
|
|
73
|
+
/**
|
|
74
|
+
* Deletes a conversation and every turn beneath it.
|
|
75
|
+
*
|
|
76
|
+
* Idempotent - one already gone answers `{ deleted: true }`, so a retry after
|
|
77
|
+
* a dropped response does not read as a failure.
|
|
78
|
+
*/
|
|
79
|
+
deleteConversation(input: {
|
|
80
|
+
conversationId: string;
|
|
81
|
+
asUser: string;
|
|
82
|
+
}): Promise<{
|
|
83
|
+
deleted: true;
|
|
84
|
+
}>;
|
|
37
85
|
/** Extract structured JSON from a file or image URL, against a platform extraction schema referenced by id. */
|
|
38
86
|
extract<T = unknown>(input: {
|
|
39
87
|
schemaId: string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type TransportOptions } from "../http.js";
|
|
2
|
+
import type { ConversationDetail, ConversationsPage } from "../../types.js";
|
|
3
|
+
export interface ListConversationsInput {
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
/**
|
|
6
|
+
* Narrow to one party's threads.
|
|
7
|
+
*
|
|
8
|
+
* Any reasonable spelling - `+919000000001`, `919000000001`,
|
|
9
|
+
* `+91 90000 00001` all resolve to the same threads, because the number is
|
|
10
|
+
* normalized server-side before the match. This is the filter to reach for
|
|
11
|
+
* beside your own customer record: you hold a phone number, and you cannot
|
|
12
|
+
* derive a conversation id from one because the id is built from a *hashed*
|
|
13
|
+
* key.
|
|
14
|
+
*/
|
|
15
|
+
contactAddress?: string;
|
|
16
|
+
status?: "open" | "snoozed" | "closed";
|
|
17
|
+
/** From a previous page's `nextCursor`. */
|
|
18
|
+
cursor?: string;
|
|
19
|
+
/** 1-50, default 20. */
|
|
20
|
+
limit?: number;
|
|
21
|
+
/** See `ActingAsOption` on the scheduling resource - same contract. */
|
|
22
|
+
asUser?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* What a workspace's agents are saying in writing.
|
|
26
|
+
*
|
|
27
|
+
* **The sibling of `calls`, with one difference that decides how it is
|
|
28
|
+
* gated: these are the *workspace's* threads, not your application's.** A call
|
|
29
|
+
* arrived on a channel you hold, so `calls.list` is scoped to your
|
|
30
|
+
* application. A conversation lives in the workspace's own subtree and belongs
|
|
31
|
+
* to it the way its contact book does - so reading one needs a live
|
|
32
|
+
* subscription in that workspace, and, when the call comes from a browser
|
|
33
|
+
* through the bridge, the signed-in member's own `agent:read`.
|
|
34
|
+
*
|
|
35
|
+
* Read-only, and deliberately. Sending on a thread is not a missing verb:
|
|
36
|
+
* inside WhatsApp's 24-hour window a reply is the agent's to make, outside it
|
|
37
|
+
* only an approved template may be sent, and posting into a thread would mean
|
|
38
|
+
* saying which of those it was and what it means for the agent still
|
|
39
|
+
* answering.
|
|
40
|
+
*/
|
|
41
|
+
export declare class ConversationsResource {
|
|
42
|
+
private readonly transport;
|
|
43
|
+
constructor(transport: TransportOptions);
|
|
44
|
+
/**
|
|
45
|
+
* The workspace's threads, newest activity first.
|
|
46
|
+
*
|
|
47
|
+
* **Page until `hasMore` is `false`; do not stop on an empty `items`.** An
|
|
48
|
+
* inbox grows without bound, so there is no total count to page against.
|
|
49
|
+
*/
|
|
50
|
+
list(input: ListConversationsInput): Promise<ConversationsPage>;
|
|
51
|
+
/**
|
|
52
|
+
* One thread and its recent messages, oldest first.
|
|
53
|
+
*
|
|
54
|
+
* **Capped, not paged** - you get the same window the agent itself sees when
|
|
55
|
+
* it composes a reply, so you are never shown less context than it used.
|
|
56
|
+
*
|
|
57
|
+
* Turns that produced no message are included and carry `failure`. Render
|
|
58
|
+
* them: a transcript that silently drops them reads as a customer being
|
|
59
|
+
* ignored, which is the opposite of what happened.
|
|
60
|
+
*
|
|
61
|
+
* Throws `ConnectError` with `code: "not_found"` for a thread this workspace
|
|
62
|
+
* does not hold.
|
|
63
|
+
*/
|
|
64
|
+
get(conversationId: string, options: {
|
|
65
|
+
workspaceId: string;
|
|
66
|
+
asUser?: string;
|
|
67
|
+
}): Promise<ConversationDetail>;
|
|
68
|
+
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -538,6 +538,54 @@ export interface ChatMessage {
|
|
|
538
538
|
role: "user" | "assistant" | "system";
|
|
539
539
|
[field: string]: unknown;
|
|
540
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* One row of Ask AI history.
|
|
543
|
+
*
|
|
544
|
+
* A conversation exists only if the turns that made it carried a
|
|
545
|
+
* `conversationId` *and* were sent on a user's behalf - see `AiResource.chat`.
|
|
546
|
+
* It belongs to one person in one of your workspaces, and never leaves your
|
|
547
|
+
* application: a workspace subscribing to two products keeps two lists.
|
|
548
|
+
*/
|
|
549
|
+
export interface AiConversation {
|
|
550
|
+
id: string;
|
|
551
|
+
/** Derived from the first thing the user said - no model call, so it costs nothing. */
|
|
552
|
+
title: string;
|
|
553
|
+
/** ISO-8601. */
|
|
554
|
+
createdAt: string;
|
|
555
|
+
/**
|
|
556
|
+
* ISO-8601, and the sort key - a conversation returned to a week later is at
|
|
557
|
+
* the top, not where it was created.
|
|
558
|
+
*/
|
|
559
|
+
updatedAt: string;
|
|
560
|
+
}
|
|
561
|
+
export interface AiConversationList {
|
|
562
|
+
conversations: AiConversation[];
|
|
563
|
+
/** Pass back as `cursor` for the next page; `null` at the end. */
|
|
564
|
+
nextCursor: string | null;
|
|
565
|
+
}
|
|
566
|
+
/** One stored turn, in the AI SDK's own shape. */
|
|
567
|
+
export interface AiConversationTurn {
|
|
568
|
+
id: string;
|
|
569
|
+
role: "user" | "assistant";
|
|
570
|
+
/**
|
|
571
|
+
* The message's parts exactly as the model produced them - prose, reasoning
|
|
572
|
+
* and tool calls, in the order they arrived.
|
|
573
|
+
*
|
|
574
|
+
* Opaque on purpose. The shape is the AI SDK's and moves with it, so a type
|
|
575
|
+
* restating it here would start rejecting transcripts the moment the SDK adds
|
|
576
|
+
* a part kind. Flattening it to text is the other mistake: the order is the
|
|
577
|
+
* model's own argument, and a flattened turn cannot be resumed.
|
|
578
|
+
*/
|
|
579
|
+
parts: unknown[];
|
|
580
|
+
/** Position in the conversation. Turns arrive in this order. */
|
|
581
|
+
sequence: number;
|
|
582
|
+
/** ISO-8601. */
|
|
583
|
+
createdAt: string;
|
|
584
|
+
}
|
|
585
|
+
export interface AiConversationDetail {
|
|
586
|
+
conversation: AiConversation;
|
|
587
|
+
turns: AiConversationTurn[];
|
|
588
|
+
}
|
|
541
589
|
export interface WorkspaceApp {
|
|
542
590
|
applicationId: string;
|
|
543
591
|
/** Stable, human-readable key - branch on this, not `applicationId`, which is per-environment. */
|
|
@@ -1822,3 +1870,85 @@ export interface CallsPage {
|
|
|
1822
1870
|
nextCursor: string | null;
|
|
1823
1871
|
hasMore: boolean;
|
|
1824
1872
|
}
|
|
1873
|
+
/**
|
|
1874
|
+
* One message thread a workspace's agents are holding.
|
|
1875
|
+
*
|
|
1876
|
+
* Hand-written rather than imported from Connect, like every other type in
|
|
1877
|
+
* this file - `conformance.test.ts` type-checks it against the server DTO, so
|
|
1878
|
+
* drift fails the build here rather than in your integration.
|
|
1879
|
+
*
|
|
1880
|
+
* Dates are ISO-8601 strings. JSON has no date type, and a UTC ISO string
|
|
1881
|
+
* sorts lexicographically in chronological order, so these compare and
|
|
1882
|
+
* range-filter without being parsed first.
|
|
1883
|
+
*/
|
|
1884
|
+
export interface ConnectConversation {
|
|
1885
|
+
id: string;
|
|
1886
|
+
/** The channel the thread arrived on. */
|
|
1887
|
+
channelId: string;
|
|
1888
|
+
/** Today always `whatsapp` - a voice channel produces calls, not threads. */
|
|
1889
|
+
kind: "whatsapp";
|
|
1890
|
+
/** The other party, normalized to E.164. */
|
|
1891
|
+
contactAddress: string;
|
|
1892
|
+
/**
|
|
1893
|
+
* The Connect contact this thread is about, once one has been matched.
|
|
1894
|
+
*
|
|
1895
|
+
* The join to your own records - store this on yours rather than matching
|
|
1896
|
+
* phone numbers. See `ContactsResource`.
|
|
1897
|
+
*/
|
|
1898
|
+
contactId: string | null;
|
|
1899
|
+
/** As the provider reported it, when it did. */
|
|
1900
|
+
contactName: string | null;
|
|
1901
|
+
status: "open" | "snoozed" | "closed";
|
|
1902
|
+
/** `null` means nothing is answering: no routing, or a human took over. */
|
|
1903
|
+
agentId: string | null;
|
|
1904
|
+
lastInboundAt: string;
|
|
1905
|
+
lastOutboundAt: string | null;
|
|
1906
|
+
lastMessagePreview: string;
|
|
1907
|
+
/**
|
|
1908
|
+
* Whether a free-form reply is still allowed, computed against **Connect's**
|
|
1909
|
+
* clock.
|
|
1910
|
+
*
|
|
1911
|
+
* WhatsApp allows one only inside 24 hours of the customer's last message;
|
|
1912
|
+
* outside it, only an approved template may be sent. Do not re-derive this
|
|
1913
|
+
* from `lastInboundAt` against your own clock - a skew shows up as a thread
|
|
1914
|
+
* your UI calls answerable when it is not.
|
|
1915
|
+
*/
|
|
1916
|
+
canReplyFreely: boolean;
|
|
1917
|
+
createdAt: string;
|
|
1918
|
+
updatedAt: string;
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Why a turn produced no message.
|
|
1922
|
+
*
|
|
1923
|
+
* `refused` is not a failure at all - it is the agent deciding not to answer,
|
|
1924
|
+
* which is what it is for.
|
|
1925
|
+
*/
|
|
1926
|
+
export type ConnectMessageFailure = "window_closed" | "no_agent" | "model_error" | "send_failed" | "refused";
|
|
1927
|
+
/** One message on a thread. A turn that sent nothing still appears, carrying `failure`. */
|
|
1928
|
+
export interface ConnectConversationMessage {
|
|
1929
|
+
id: string;
|
|
1930
|
+
direction: "inbound" | "outbound";
|
|
1931
|
+
/**
|
|
1932
|
+
* Who produced it.
|
|
1933
|
+
*
|
|
1934
|
+
* `agent` and `human` are both outbound and deliberately distinguished - a
|
|
1935
|
+
* thread that cannot show which replies were automatic is one nobody trusts
|
|
1936
|
+
* enough to leave running. `system` is neither party: a turn Connect itself
|
|
1937
|
+
* recorded, which is what a `failure` row is.
|
|
1938
|
+
*/
|
|
1939
|
+
author: "contact" | "agent" | "human" | "system";
|
|
1940
|
+
text: string;
|
|
1941
|
+
failure: ConnectMessageFailure | null;
|
|
1942
|
+
createdAt: string;
|
|
1943
|
+
}
|
|
1944
|
+
/** A thread and its recent messages, oldest first. Capped rather than paged. */
|
|
1945
|
+
export interface ConversationDetail {
|
|
1946
|
+
conversation: ConnectConversation;
|
|
1947
|
+
messages: ConnectConversationMessage[];
|
|
1948
|
+
}
|
|
1949
|
+
/** One page of threads, newest activity first. Page until `hasMore` is `false`. */
|
|
1950
|
+
export interface ConversationsPage {
|
|
1951
|
+
items: ConnectConversation[];
|
|
1952
|
+
nextCursor: string | null;
|
|
1953
|
+
hasMore: boolean;
|
|
1954
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmoops/connect-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.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",
|