@cosmoops/connect-sdk 0.30.1 → 0.33.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 +267 -0
- package/dist/cjs/client/components/visitor-chat.js +387 -0
- package/dist/cjs/client/components/visitor-stream.js +124 -0
- package/dist/cjs/client/index.js +23 -3
- package/dist/cjs/client/provider.js +24 -0
- package/dist/cjs/client/use-push.js +139 -0
- package/dist/cjs/server/client.js +15 -1
- package/dist/cjs/server/handler.js +196 -0
- package/dist/cjs/server/resources/ai.js +85 -0
- package/dist/cjs/server/resources/enrichment.js +107 -0
- package/dist/cjs/server/resources/push.js +92 -0
- package/dist/cjs/server/resources/visitor.js +227 -0
- package/dist/esm/client/components/visitor-chat.js +384 -0
- package/dist/esm/client/components/visitor-stream.js +120 -0
- package/dist/esm/client/index.js +17 -1
- package/dist/esm/client/provider.js +23 -0
- package/dist/esm/client/use-push.js +136 -0
- package/dist/esm/server/client.js +15 -1
- package/dist/esm/server/handler.js +196 -0
- package/dist/esm/server/resources/ai.js +85 -0
- package/dist/esm/server/resources/enrichment.js +107 -0
- package/dist/esm/server/resources/push.js +88 -0
- package/dist/esm/server/resources/visitor.js +222 -0
- package/dist/types/client/components/visitor-chat.d.ts +120 -0
- package/dist/types/client/components/visitor-stream.d.ts +42 -0
- package/dist/types/client/index.d.ts +13 -1
- package/dist/types/client/provider.d.ts +21 -0
- package/dist/types/client/use-push.d.ts +56 -0
- package/dist/types/server/client.d.ts +13 -1
- package/dist/types/server/handler.d.ts +0 -24
- package/dist/types/server/resources/ai.d.ts +75 -1
- package/dist/types/server/resources/enrichment.d.ts +102 -1
- package/dist/types/server/resources/push.d.ts +73 -0
- package/dist/types/server/resources/visitor.d.ts +200 -0
- package/dist/types/types.d.ts +683 -0
- package/package.json +3 -2
|
@@ -107,5 +107,90 @@ class AiResource {
|
|
|
107
107
|
});
|
|
108
108
|
return result.data;
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Draft every section of a platform-defined assessment from one sentence.
|
|
112
|
+
*
|
|
113
|
+
* The intended flow is draft first, then let the person correct it section
|
|
114
|
+
* by section: a blank questionnaire is the reason most people abandon one,
|
|
115
|
+
* and editing a strawman is a far smaller ask than authoring from nothing.
|
|
116
|
+
*
|
|
117
|
+
* Unattributed - assessments run against platform-wide definitions and the
|
|
118
|
+
* first consumers run before anyone has signed in, so there is no `asUser`
|
|
119
|
+
* and no workspace here.
|
|
120
|
+
*/
|
|
121
|
+
async draftAssessment(input) {
|
|
122
|
+
const result = await (0, http_js_1.request)(this.transport, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
path: "/ai/assessment/draft",
|
|
125
|
+
json: input,
|
|
126
|
+
});
|
|
127
|
+
return result.data;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Critique a completed assessment - the tensions between sections, the
|
|
131
|
+
* riskiest assumption, and a coarse score.
|
|
132
|
+
*
|
|
133
|
+
* Unknown section keys in `answers` are ignored rather than rejected, so a
|
|
134
|
+
* definition changing under a half-finished assessment costs the reader
|
|
135
|
+
* nothing.
|
|
136
|
+
*/
|
|
137
|
+
async reviewAssessment(input) {
|
|
138
|
+
const result = await (0, http_js_1.request)(this.transport, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
path: "/ai/assessment/review",
|
|
141
|
+
json: input,
|
|
142
|
+
});
|
|
143
|
+
return result.data;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* How ready the site at a domain is for AI agents.
|
|
147
|
+
*
|
|
148
|
+
* Probes for an MCP server, an OpenAPI description, `llms.txt`, structured
|
|
149
|
+
* data and a markdown twin; evaluates the site's `robots.txt` against the
|
|
150
|
+
* assistants that actually fetch pages; checks whether its content survives
|
|
151
|
+
* without JavaScript and whether a bot wall refuses agents outright. Then
|
|
152
|
+
* grades what it found and says what to fix first.
|
|
153
|
+
*
|
|
154
|
+
* Needs an API key scoped to **`ai:readiness`**, which is separate from every
|
|
155
|
+
* other AI scope because it is the widest outbound surface on the API - it
|
|
156
|
+
* POSTs to candidate MCP endpoints and fetches the home page under two user
|
|
157
|
+
* agents.
|
|
158
|
+
*
|
|
159
|
+
* ```ts
|
|
160
|
+
* const result = await connect.ai.readiness({ domain: "acme.com" });
|
|
161
|
+
* if (result.status === "assessed") {
|
|
162
|
+
* console.log(result.report.grade, result.report.topFix?.fix);
|
|
163
|
+
* }
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* Four things to know before wiring it in:
|
|
167
|
+
*
|
|
168
|
+
* - **Set your client timeout to at least 90 seconds.** A cold assessment
|
|
169
|
+
* runs the whole probe before answering and can take up to a minute; many
|
|
170
|
+
* HTTP clients default to 30s and abandon a request that was about to
|
|
171
|
+
* succeed. Cached reads return in milliseconds and are the normal case -
|
|
172
|
+
* reports are held for a week.
|
|
173
|
+
* - **A week, not ninety days**, because a report is a to-do list somebody is
|
|
174
|
+
* working through. Pass `refresh` after shipping a fix to see it land.
|
|
175
|
+
* - **Handle `status: "error"` checks as unknown, not as failures.** They mean
|
|
176
|
+
* Connect could not measure that check, and they are excluded from the
|
|
177
|
+
* score rather than counted against it.
|
|
178
|
+
* - **`skipped` says whose problem it is.** `unreachable` means the site did
|
|
179
|
+
* not answer; `provider_unavailable` means Connect's own assessment service
|
|
180
|
+
* did. Only the second is worth retrying silently, and neither should be
|
|
181
|
+
* shown as a failing grade.
|
|
182
|
+
*/
|
|
183
|
+
readiness(input, options = {}) {
|
|
184
|
+
return (0, http_js_1.request)(this.transport, {
|
|
185
|
+
method: "POST",
|
|
186
|
+
path: "/ai/readiness",
|
|
187
|
+
json: {
|
|
188
|
+
domain: input.domain,
|
|
189
|
+
...(input.refresh ? { refresh: true } : {}),
|
|
190
|
+
},
|
|
191
|
+
headers: (0, acting_as_js_1.actingAs)(input.asUser),
|
|
192
|
+
signal: options.signal,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
110
195
|
}
|
|
111
196
|
exports.AiResource = AiResource;
|
|
@@ -52,5 +52,112 @@ class EnrichmentResource {
|
|
|
52
52
|
signal: options.signal,
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* What the company at a domain **looks like** - logo, palette, typefaces,
|
|
57
|
+
* and the radius, spacing and breakpoint scales its own stylesheets declare.
|
|
58
|
+
*
|
|
59
|
+
* The sibling of `company()` above: that one answers what a company does,
|
|
60
|
+
* this one answers what it looks like. Useful for white-labelling a portal
|
|
61
|
+
* to a customer's brand, pre-filling a design brief, or showing a real
|
|
62
|
+
* account card instead of a grey placeholder.
|
|
63
|
+
*
|
|
64
|
+
* Needs an API key scoped to **`enrichment:brand`** - separate from
|
|
65
|
+
* `enrichment:read` because it is far more expensive: it crawls
|
|
66
|
+
* stylesheets, may render the page in a headless browser, and caches image
|
|
67
|
+
* bytes.
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* const result = await connect.enrichment.brand({ domain: "acme.com" });
|
|
71
|
+
* if (result.status === "extracted") {
|
|
72
|
+
* const primary = result.profile.brandKit.palette.find((c) => c.role === "primary");
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* Three things to know before wiring it in:
|
|
77
|
+
*
|
|
78
|
+
* - **Set your client timeout to at least 90 seconds.** A cold read runs
|
|
79
|
+
* the whole extraction before answering and can take up to a minute; many
|
|
80
|
+
* HTTP clients default to 30s and abandon a request that was about to
|
|
81
|
+
* succeed. Cached reads return in milliseconds, and cached is the normal
|
|
82
|
+
* case - profiles are held for 90 days, so you pay the cold path roughly
|
|
83
|
+
* once per domain.
|
|
84
|
+
* - **Never store `brandKit.logos[].url`.** It is a short-lived signed URL
|
|
85
|
+
* minted per read. Render it directly or re-read the profile; persist
|
|
86
|
+
* `sourceUrl` if you need something stable.
|
|
87
|
+
* - **The values were measured, not generated.** Check `palette[].source` -
|
|
88
|
+
* a `css-var` entry is the company naming its own colour, a `frequency`
|
|
89
|
+
* entry is a ranked count and much weaker.
|
|
90
|
+
*/
|
|
91
|
+
brand(input, options = {}) {
|
|
92
|
+
return (0, http_js_1.request)(this.transport, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
path: "/enrichment/brand",
|
|
95
|
+
json: {
|
|
96
|
+
domain: input.domain,
|
|
97
|
+
...(input.refresh ? { refresh: true } : {}),
|
|
98
|
+
},
|
|
99
|
+
headers: (0, acting_as_js_1.actingAs)(input.asUser),
|
|
100
|
+
signal: options.signal,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* How **visible** the business at a domain is online, scored out of 100.
|
|
105
|
+
*
|
|
106
|
+
* The third sibling of `company()` and `brand()`: that one answers what a
|
|
107
|
+
* business does, the other what it looks like, and this one answers how
|
|
108
|
+
* findable it is - by a search engine, by a customer, and by the assistants
|
|
109
|
+
* that increasingly answer questions about a business before anybody visits
|
|
110
|
+
* its site.
|
|
111
|
+
*
|
|
112
|
+
* Needs an API key scoped to **`enrichment:presence`** - separate from
|
|
113
|
+
* `enrichment:read` and `enrichment:brand` because it is the widest outbound
|
|
114
|
+
* read of the three: roughly twenty fetches across the site's well-known
|
|
115
|
+
* paths, and sometimes a headless browser.
|
|
116
|
+
*
|
|
117
|
+
* ```ts
|
|
118
|
+
* const result = await connect.enrichment.presence({ domain: "acme.com" });
|
|
119
|
+
* if (result.status === "scanned") {
|
|
120
|
+
* for (const next of result.scan.recommendations) {
|
|
121
|
+
* console.log(`${next.priority}. ${next.title} - ${next.fix}`);
|
|
122
|
+
* }
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* Four things to know before wiring it in:
|
|
127
|
+
*
|
|
128
|
+
* - **The score is computed, not generated.** Every point comes from a check
|
|
129
|
+
* run against something actually observed, and every check ships with its
|
|
130
|
+
* weight, what was seen and the URL that settles it - so the total is
|
|
131
|
+
* reconstructable. A model only orders `recommendations` and writes the
|
|
132
|
+
* prose; it cannot move the number. That is the whole reason this is worth
|
|
133
|
+
* showing a customer: ask a model how good a company's web presence is and
|
|
134
|
+
* it answers 72 with total confidence for a site it never read.
|
|
135
|
+
* - **`social` is claimed, never verified.** Those are URLs the site itself
|
|
136
|
+
* printed. Nothing proves the account belongs to that business - render it
|
|
137
|
+
* as "linked from their site". Check `machineReadable` too: `false` means
|
|
138
|
+
* a real profile that search engines have to infer from a footer link,
|
|
139
|
+
* which is a specific, common and easily fixed finding.
|
|
140
|
+
* - **A low score is a success, not a skip.** Every check failing is the most
|
|
141
|
+
* useful report this produces. Only `unreachable` means the site did not
|
|
142
|
+
* answer - and `provider_unavailable` means *Connect* did not, which is
|
|
143
|
+
* never something to tell your user about their own site.
|
|
144
|
+
* - **Set your client timeout to at least 90 seconds.** A cold scan runs the
|
|
145
|
+
* whole probe before answering; many HTTP clients default to 30s and
|
|
146
|
+
* abandon a request that was about to succeed. Cached scans return in
|
|
147
|
+
* milliseconds, and cached is the normal case - results are held for a
|
|
148
|
+
* fortnight, so you pay the cold path roughly once per domain.
|
|
149
|
+
*/
|
|
150
|
+
presence(input, options = {}) {
|
|
151
|
+
return (0, http_js_1.request)(this.transport, {
|
|
152
|
+
method: "POST",
|
|
153
|
+
path: "/enrichment/presence",
|
|
154
|
+
json: {
|
|
155
|
+
domain: input.domain,
|
|
156
|
+
...(input.refresh ? { refresh: true } : {}),
|
|
157
|
+
},
|
|
158
|
+
headers: (0, acting_as_js_1.actingAs)(input.asUser),
|
|
159
|
+
signal: options.signal,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
55
162
|
}
|
|
56
163
|
exports.EnrichmentResource = EnrichmentResource;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PushResource = void 0;
|
|
4
|
+
const http_js_1 = require("../http.js");
|
|
5
|
+
const acting_as_js_1 = require("./acting-as.js");
|
|
6
|
+
/**
|
|
7
|
+
* Web Push, with Connect holding everything that makes it work.
|
|
8
|
+
*
|
|
9
|
+
* Your application supplies a title, a body, a URL and the members to reach.
|
|
10
|
+
* Connect owns the VAPID keys it signs with, the devices each member has
|
|
11
|
+
* registered across every application on the platform, that member's own
|
|
12
|
+
* notification preferences, and the bookkeeping when a browser goes away.
|
|
13
|
+
*
|
|
14
|
+
* So you never hold a push credential, never store an endpoint, and cannot
|
|
15
|
+
* reach anybody who is not an active member of the workspace you name -
|
|
16
|
+
* membership is re-checked on every send rather than trusted from the ids you
|
|
17
|
+
* pass.
|
|
18
|
+
*
|
|
19
|
+
* ## Two halves, and only `send` belongs on your server
|
|
20
|
+
*
|
|
21
|
+
* Registering a device happens in the **browser**, because a subscription is
|
|
22
|
+
* produced by `pushManager.subscribe()` against the member's own browser and
|
|
23
|
+
* no server can make one for them. `registerDevice` and `revokeDevice` below
|
|
24
|
+
* therefore take a session token and are what the SDK bridge calls on the
|
|
25
|
+
* browser's behalf - see `createConnectHandler`. You will normally reach them
|
|
26
|
+
* through `useConnectPush()` in `@cosmoops/connect-sdk/client` rather than
|
|
27
|
+
* directly.
|
|
28
|
+
*/
|
|
29
|
+
class PushResource {
|
|
30
|
+
transport;
|
|
31
|
+
constructor(transport) {
|
|
32
|
+
this.transport = transport;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Notify members of a workspace.
|
|
36
|
+
*
|
|
37
|
+
* **`delivered: 0` is a success, not a failure.** The member may have
|
|
38
|
+
* registered no device, silenced notifications from their apps, or denied
|
|
39
|
+
* the browser prompt - all ordinary. A caller that retries on zero will
|
|
40
|
+
* retry forever against somebody who has opted out.
|
|
41
|
+
*
|
|
42
|
+
* Treat every send as a nudge toward something already true in your own
|
|
43
|
+
* data, and never let a failure here fail the thing that triggered it: the
|
|
44
|
+
* reminder is worth less than the record it is about.
|
|
45
|
+
*
|
|
46
|
+
* `url` must be absolute and on an origin this application registered as an
|
|
47
|
+
* SSO redirect URI. Connect refuses anything else, because a URL it did not
|
|
48
|
+
* check is an open redirect delivered to a lock screen.
|
|
49
|
+
*/
|
|
50
|
+
send(input) {
|
|
51
|
+
return (0, http_js_1.request)(this.transport, { method: "POST", path: "/push", json: input });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The VAPID public key a browser needs before it can subscribe, and whether
|
|
55
|
+
* the platform can send at all.
|
|
56
|
+
*
|
|
57
|
+
* Safe to hand to any signed-in caller: it is the verification half of the
|
|
58
|
+
* pair, so holding it lets a browser *receive* what Connect signs and never
|
|
59
|
+
* send anything.
|
|
60
|
+
*/
|
|
61
|
+
key(options) {
|
|
62
|
+
return (0, http_js_1.request)(this.transport, {
|
|
63
|
+
method: "GET",
|
|
64
|
+
path: "/push/subscriptions",
|
|
65
|
+
headers: (0, acting_as_js_1.actingAs)(options.asUser),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Register one browser, from a subscription that browser just produced. */
|
|
69
|
+
registerDevice(input, options) {
|
|
70
|
+
return (0, http_js_1.request)(this.transport, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
path: "/push/subscriptions",
|
|
73
|
+
json: input,
|
|
74
|
+
headers: (0, acting_as_js_1.actingAs)(options.asUser),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Stop notifications on one browser.
|
|
79
|
+
*
|
|
80
|
+
* The id is the one `registerDevice` returned. Connect re-checks that it
|
|
81
|
+
* belongs to the calling member, so a guessed id is a refusal rather than
|
|
82
|
+
* somebody else's device going quiet.
|
|
83
|
+
*/
|
|
84
|
+
revokeDevice(id, options) {
|
|
85
|
+
return (0, http_js_1.request)(this.transport, {
|
|
86
|
+
method: "DELETE",
|
|
87
|
+
path: `/push/subscriptions?id=${encodeURIComponent(id)}`,
|
|
88
|
+
headers: (0, acting_as_js_1.actingAs)(options.asUser),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.PushResource = PushResource;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VisitorKnowledgeResource = exports.VisitorResource = void 0;
|
|
4
|
+
const http_js_1 = require("../http.js");
|
|
5
|
+
/**
|
|
6
|
+
* The header a bridge forwards the visitor's own address on.
|
|
7
|
+
*
|
|
8
|
+
* It exists because `x-forwarded-for` cannot do this job here: by the time your
|
|
9
|
+
* backend calls Connect, that header describes the hop from *your server*, and
|
|
10
|
+
* the address Connect's per-visitor limiter needs is the one on the hop before
|
|
11
|
+
* it - which only your bridge has seen.
|
|
12
|
+
*
|
|
13
|
+
* **What it is and is not trusted for.** It partitions your own application's
|
|
14
|
+
* budget fairly between visitors; it does not grant anything. A caller sending a
|
|
15
|
+
* fresh value per request defeats only the per-visitor limiter and is still
|
|
16
|
+
* bounded by the per-key burst limit and the per-application daily ceiling -
|
|
17
|
+
* which are the two that actually cap spending, and neither is client-supplied.
|
|
18
|
+
* Omit it and every visitor shares one bucket, which is a stricter limit rather
|
|
19
|
+
* than an exemption.
|
|
20
|
+
*/
|
|
21
|
+
const VISITOR_IP_HEADER = "x-connect-visitor-ip";
|
|
22
|
+
function visitorHeaders(options) {
|
|
23
|
+
return options?.visitorIp ? { [VISITOR_IP_HEADER]: options.visitorIp } : {};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The public chat bubble on your own marketing site.
|
|
27
|
+
*
|
|
28
|
+
* The one resource here whose caller is **not** one of your users. There is no
|
|
29
|
+
* `asUser`, no `workspaceId` and no session anywhere in it: the counterpart is a
|
|
30
|
+
* stranger who has no account, and everything the assistant may say or do is
|
|
31
|
+
* configuration a platform admin set on your application record.
|
|
32
|
+
*
|
|
33
|
+
* That is why none of these methods takes an id naming anything. The workspace a
|
|
34
|
+
* call books into, the event type it books, the knowledge it answers from and the
|
|
35
|
+
* daily budget it spends are all read from your application - so nothing a
|
|
36
|
+
* visitor types, and nothing an injection attempt in your own chat window
|
|
37
|
+
* achieves, reaches a different tenant.
|
|
38
|
+
*
|
|
39
|
+
* **Never call these from a browser.** They take your API key, like every other
|
|
40
|
+
* resource on this client. `createConnectHandler` already exposes them on your
|
|
41
|
+
* own origin for `<ConnectVisitorChat>` to use, which is how the credential stays
|
|
42
|
+
* server-side.
|
|
43
|
+
*/
|
|
44
|
+
class VisitorResource {
|
|
45
|
+
transport;
|
|
46
|
+
/**
|
|
47
|
+
* The knowledge base behind the assistant - see `VisitorKnowledgeResource`.
|
|
48
|
+
*
|
|
49
|
+
* Nested rather than a top-level resource, because it is meaningless on its
|
|
50
|
+
* own: it exists to decide what *this* assistant may say.
|
|
51
|
+
*/
|
|
52
|
+
knowledge;
|
|
53
|
+
constructor(transport) {
|
|
54
|
+
this.transport = transport;
|
|
55
|
+
this.knowledge = new VisitorKnowledgeResource(transport);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Open a conversation, or reopen one.
|
|
59
|
+
*
|
|
60
|
+
* Generates nothing - the greeting and the suggested questions come off the
|
|
61
|
+
* application record - so this is the one call in the flow that is always fast,
|
|
62
|
+
* which is what lets a bubble paint real copy instead of a spinner.
|
|
63
|
+
*
|
|
64
|
+
* Pass `resume` with a `sessionId` you kept and the transcript comes back with
|
|
65
|
+
* it. A `resume` that is expired, unknown or another application's silently
|
|
66
|
+
* starts a fresh conversation rather than failing: the token is the only thing
|
|
67
|
+
* being offered and it is being rejected, so there is nothing for the visitor
|
|
68
|
+
* to do about an error.
|
|
69
|
+
*/
|
|
70
|
+
startSession(input, options = {}) {
|
|
71
|
+
return (0, http_js_1.request)(this.transport, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
path: "/visitor/session",
|
|
74
|
+
json: {
|
|
75
|
+
workspaceId: input.workspaceId,
|
|
76
|
+
page: input.page ?? null,
|
|
77
|
+
resume: input.resume ?? null,
|
|
78
|
+
},
|
|
79
|
+
headers: visitorHeaders(options),
|
|
80
|
+
signal: options.signal,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* One turn, streamed.
|
|
85
|
+
*
|
|
86
|
+
* Returns the raw `Response` rather than a parsed body, the same way
|
|
87
|
+
* `ai.chat` does: this answers server-sent events, and buffering them here
|
|
88
|
+
* would throw away the only reason to stream. The events are `delta`
|
|
89
|
+
* (`{text}`), `done` (`VisitorTurnDone`) and `error` (`{message}`).
|
|
90
|
+
*
|
|
91
|
+
* **The transcript is not a parameter**, which is the security property this
|
|
92
|
+
* endpoint is built around: the caller is ultimately a browser on a public
|
|
93
|
+
* page, and a client that can supply its own transcript can supply one in
|
|
94
|
+
* which anything has already been established. Only the conversation id and
|
|
95
|
+
* the new message travel.
|
|
96
|
+
*/
|
|
97
|
+
chat(input, options = {}) {
|
|
98
|
+
return (0, http_js_1.request)(this.transport, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
path: "/visitor/chat",
|
|
101
|
+
json: input,
|
|
102
|
+
headers: visitorHeaders(options),
|
|
103
|
+
raw: true,
|
|
104
|
+
signal: options.signal,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Book the call a visitor chose.
|
|
109
|
+
*
|
|
110
|
+
* `startAt` must be a time the turn's own slot lookup returned - an arbitrary
|
|
111
|
+
* instant is refused. That, plus a name and phone collected in real fields,
|
|
112
|
+
* is what makes it impossible to book an appointment at a time nobody offered
|
|
113
|
+
* or with a phone number a model parsed out of prose.
|
|
114
|
+
*
|
|
115
|
+
* A conversation books at most one appointment; a second call answers `409`.
|
|
116
|
+
*/
|
|
117
|
+
book(input, options = {}) {
|
|
118
|
+
return (0, http_js_1.request)(this.transport, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
path: "/visitor/book",
|
|
121
|
+
headers: visitorHeaders(options),
|
|
122
|
+
signal: options.signal,
|
|
123
|
+
json: {
|
|
124
|
+
...input,
|
|
125
|
+
startAt: input.startAt instanceof Date ? input.startAt.toISOString() : input.startAt,
|
|
126
|
+
email: input.email ?? null,
|
|
127
|
+
notes: input.notes ?? null,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.VisitorResource = VisitorResource;
|
|
133
|
+
/**
|
|
134
|
+
* A subscribing workspace's **public** knowledge base - what their website chat
|
|
135
|
+
* may tell strangers.
|
|
136
|
+
*
|
|
137
|
+
* ## It is deliberately not their Context Index
|
|
138
|
+
*
|
|
139
|
+
* `ai.chat` answers from a workspace's Drive-fed corpus, for a signed-in member
|
|
140
|
+
* of that workspace. This is a separate store that holds only what somebody
|
|
141
|
+
* explicitly published, and the separation is the reason a publishable widget
|
|
142
|
+
* credential is safe to publish: everything the widget can reach was already
|
|
143
|
+
* meant for the public.
|
|
144
|
+
*
|
|
145
|
+
* Do not try to fill it by uploading to Drive. Nothing reaches this store as a
|
|
146
|
+
* side effect of anything else - that is the invariant, not an inconvenience.
|
|
147
|
+
*
|
|
148
|
+
* ## Publishing is its own call
|
|
149
|
+
*
|
|
150
|
+
* `save` always writes a draft. `publish` is what makes text readable by
|
|
151
|
+
* strangers, so no edit can do it by accident, and `unpublish` can fail - which
|
|
152
|
+
* means the item is **still live**, not that the call was pointless.
|
|
153
|
+
*/
|
|
154
|
+
class VisitorKnowledgeResource {
|
|
155
|
+
transport;
|
|
156
|
+
constructor(transport) {
|
|
157
|
+
this.transport = transport;
|
|
158
|
+
}
|
|
159
|
+
/** Everything, drafts included - the list a customer audits. */
|
|
160
|
+
list(workspaceId) {
|
|
161
|
+
return (0, http_js_1.request)(this.transport, {
|
|
162
|
+
method: "GET",
|
|
163
|
+
path: "/visitor/knowledge",
|
|
164
|
+
query: { workspaceId },
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Create or edit a draft.
|
|
169
|
+
*
|
|
170
|
+
* A body over the cap is **refused rather than truncated** - split a long page
|
|
171
|
+
* into sections, which is also what makes retrieval work.
|
|
172
|
+
*/
|
|
173
|
+
async save(input) {
|
|
174
|
+
const result = await (0, http_js_1.request)(this.transport, {
|
|
175
|
+
method: "POST",
|
|
176
|
+
path: "/visitor/knowledge",
|
|
177
|
+
json: {
|
|
178
|
+
...input,
|
|
179
|
+
itemId: input.itemId ?? null,
|
|
180
|
+
sourceRef: input.sourceRef ?? null,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
return result.item;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Make one item readable by strangers.
|
|
187
|
+
*
|
|
188
|
+
* May be slow: the publish that outgrows the prompt provisions a retrieval
|
|
189
|
+
* corpus and indexes everything published so far. An indexing failure still
|
|
190
|
+
* publishes and comes back with `indexState: "failed"` and `indexError` set,
|
|
191
|
+
* so a provider hiccup never makes your button look broken.
|
|
192
|
+
*/
|
|
193
|
+
async publish(input) {
|
|
194
|
+
const result = await (0, http_js_1.request)(this.transport, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
path: "/visitor/knowledge/publish",
|
|
197
|
+
json: input,
|
|
198
|
+
});
|
|
199
|
+
return result.item;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Take one item out of what strangers can read.
|
|
203
|
+
*
|
|
204
|
+
* **A failure means it is still live.** The retrievable copy is removed before
|
|
205
|
+
* the record is flipped, so a provider that refuses the delete aborts rather
|
|
206
|
+
* than leaving an item your screen shows as withdrawn that the widget goes on
|
|
207
|
+
* quoting. Surface the error and let them retry; do not grey the row out
|
|
208
|
+
* optimistically.
|
|
209
|
+
*/
|
|
210
|
+
async unpublish(input) {
|
|
211
|
+
const result = await (0, http_js_1.request)(this.transport, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
path: "/visitor/knowledge/unpublish",
|
|
214
|
+
json: input,
|
|
215
|
+
});
|
|
216
|
+
return result.item;
|
|
217
|
+
}
|
|
218
|
+
/** Remove it entirely. A published item is unpublished first. */
|
|
219
|
+
async delete(input) {
|
|
220
|
+
await (0, http_js_1.request)(this.transport, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
path: "/visitor/knowledge/delete",
|
|
223
|
+
json: input,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
exports.VisitorKnowledgeResource = VisitorKnowledgeResource;
|