viasocket-apps 0.4.0 → 0.6.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/README.md +38 -0
- package/dist/cjs/browser.js +2 -0
- package/dist/cjs/client.js +91 -0
- package/dist/cjs/index.js +1 -0
- package/dist/esm/browser.d.ts +7 -0
- package/dist/esm/browser.js +2 -0
- package/dist/esm/client.d.ts +24 -1
- package/dist/esm/client.js +91 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/types.d.ts +113 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -104,6 +104,9 @@ import { connect } from 'viasocket-apps/browser'
|
|
|
104
104
|
// for their scopes; skipActionSelection goes straight to the app's consent screen.
|
|
105
105
|
const { authId } = await connect({ embedToken, serviceId: 'rowbu58rc', actions: ['<action_id>'], skipActionSelection: true })
|
|
106
106
|
// Send authId to your backend, which enables the app and stores the script_id.
|
|
107
|
+
|
|
108
|
+
// When user.listConnections() shows a connection with isExpired: true, sign in to it again:
|
|
109
|
+
await connect({ embedToken, serviceId: 'rowbu58rc', authId }) // updates it in place; resolves with the same authId
|
|
107
110
|
```
|
|
108
111
|
|
|
109
112
|
`serviceId` is the app's `service_id` exactly as the catalog returns it — a bare id such as
|
|
@@ -118,6 +121,38 @@ popup is on the app's own sign-in page a close can only be inferred, and arrives
|
|
|
118
121
|
later; if the user then finishes anyway, `onLateSuccess` is called with the same result a resolved
|
|
119
122
|
`connect()` gives. Treat `'closed'` as "nothing yet", not as a verdict.
|
|
120
123
|
|
|
124
|
+
## AI assistant
|
|
125
|
+
|
|
126
|
+
A chat assistant we run for each of your users: threads and history kept per user, your prompt per
|
|
127
|
+
call, your own endpoints as its tools — one of which can run an action in the user's apps. Backend
|
|
128
|
+
only, like everything else here: the prompt and the tools' headers are yours.
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
const reply = await user.ai.send({
|
|
132
|
+
threadId: 'support-7f3a9c', // your own string per conversation; omit to start one
|
|
133
|
+
message: 'Where is the order for customer 4471?',
|
|
134
|
+
prompt: 'You are an order assistant. Use lookup_order to fetch orders. Answer in one sentence.',
|
|
135
|
+
tools: [
|
|
136
|
+
{
|
|
137
|
+
name: 'lookup_order',
|
|
138
|
+
description: 'Fetches an order by customer id',
|
|
139
|
+
url: 'https://api.your-product.com/orders/lookup',
|
|
140
|
+
headers: { Authorization: `Bearer ${process.env.ORDERS_API_KEY}` },
|
|
141
|
+
fields: { id: { type: 'string', description: 'Customer id to look up' } },
|
|
142
|
+
requiredParams: ['id']
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
})
|
|
146
|
+
reply.content // the answer; with responseType 'json_object' or 'json_schema', the parsed object
|
|
147
|
+
|
|
148
|
+
const threads = await user.ai.threads() // [{ threadId, title, updatedAt }] — title is null until the AI writes it
|
|
149
|
+
const page = await user.ai.history(threadId, { page: 1 }) // { messages, page, hasMore } — 40 exchanges a page, page 1 the latest
|
|
150
|
+
const socket = await user.ai.rtlayerToken() // for a chat UI: the browser subscribes with it, then you send with delivery: 'rtlayer'
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The whole contract — tools, JSON answers, the websocket path and what arrives on it — is
|
|
154
|
+
[flow.viasocket.com/documentation/ai.md](https://flow.viasocket.com/documentation/ai.md).
|
|
155
|
+
|
|
121
156
|
## Methods
|
|
122
157
|
|
|
123
158
|
| Call | What it does |
|
|
@@ -172,6 +207,9 @@ minutes between checks, as a string: `"5"` or `"15"`.
|
|
|
172
207
|
| `user.disableFlow(scriptId)` / `user.enableFlow(scriptId)` | Turn a flow off or back on. Disabling a subscription ends it. |
|
|
173
208
|
| `user.listConnections()` | Every app this user has connected. |
|
|
174
209
|
| `user.revokeConnection(authId)` | Disconnect an app. Disable its flows first. |
|
|
210
|
+
| `user.ai.send({ message, threadId, prompt, tools, responseType, delivery })` | One message to the assistant we run for this user. Returns `{ threadId, messageId, content, finishReason }`, or `{ threadId, messageId, channel }` with `delivery: 'rtlayer'`. |
|
|
211
|
+
| `user.ai.threads()` / `user.ai.history(threadId, { page })` | This user's threads, newest first (`title` is `null` until the AI writes it); one thread's messages, 40 exchanges a page. |
|
|
212
|
+
| `user.ai.rtlayerToken()` | A 48-hour subscribe token for the browser, with the `channelPrefix` every thread's channel starts with. |
|
|
175
213
|
| `signEmbedToken({ orgId, projectId, uniqueIdentifier, secret })` | The signing step on its own, if you want it without a client. |
|
|
176
214
|
|
|
177
215
|
Every failure — a non-2xx status or a body with `success: false` — throws a `ViaSocketError`
|
package/dist/cjs/browser.js
CHANGED
|
@@ -144,6 +144,8 @@ async function connect(options) {
|
|
|
144
144
|
scriptOptions.filteredActions = options.actions;
|
|
145
145
|
if (options.skipActionSelection)
|
|
146
146
|
scriptOptions.skipActionSelection = true;
|
|
147
|
+
if (options.authId)
|
|
148
|
+
scriptOptions.authId = options.authId;
|
|
147
149
|
if (Object.keys(scriptOptions).length)
|
|
148
150
|
open(options.embedToken, serviceId, scriptOptions);
|
|
149
151
|
else
|
package/dist/cjs/client.js
CHANGED
|
@@ -14,6 +14,9 @@ const errors_js_1 = require("./errors.js");
|
|
|
14
14
|
const http_js_1 = require("./http.js");
|
|
15
15
|
const token_js_1 = require("./token.js");
|
|
16
16
|
const DEFAULT_API_BASE_URL = 'https://flow-api.viasocket.com';
|
|
17
|
+
/** The AI API's thread id: the product's own string. */
|
|
18
|
+
const AI_THREAD_ID = /^[A-Za-z0-9_-]{1,100}$/;
|
|
19
|
+
const AI_MAX_TOOLS = 20;
|
|
17
20
|
const DEFAULT_RUN_BASE_URL = 'https://flow.sokt.io';
|
|
18
21
|
/** The public catalog: no token, the same data in every environment. */
|
|
19
22
|
const CATALOG = {
|
|
@@ -285,6 +288,94 @@ class UserScope {
|
|
|
285
288
|
enableFlow(scriptId) {
|
|
286
289
|
return this.setFlowStatus(scriptId, 1);
|
|
287
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* The AI API: a chat assistant viaSocket runs for this user — threads and history kept per
|
|
293
|
+
* `unique_identifier`, your prompt per call, your own endpoints as its tools. Backend only, like
|
|
294
|
+
* everything else here: the prompt and the tools' headers are yours to keep off the browser.
|
|
295
|
+
* The whole contract is https://flow.viasocket.com/documentation/ai.md.
|
|
296
|
+
*/
|
|
297
|
+
ai = {
|
|
298
|
+
/**
|
|
299
|
+
* Sends one message and returns the answer — or, with `delivery: 'rtlayer'`, the 202's
|
|
300
|
+
* `channel` the answer will be published on (subscribe to it in the browser before sending).
|
|
301
|
+
* Refuses, before any request, what the API would refuse: a threadId with other characters,
|
|
302
|
+
* `rtlayer` without a threadId, a model without its service, a jsonSchema without its responseType.
|
|
303
|
+
*/
|
|
304
|
+
send: (async (params) => {
|
|
305
|
+
const body = { message: required('message', params?.message) };
|
|
306
|
+
if (params.threadId !== undefined) {
|
|
307
|
+
if (!AI_THREAD_ID.test(params.threadId)) {
|
|
308
|
+
throw new errors_js_1.ViaSocketError('ai.send: threadId is letters, digits, - and _, up to 100 characters', { status: null });
|
|
309
|
+
}
|
|
310
|
+
body.thread_id = params.threadId;
|
|
311
|
+
}
|
|
312
|
+
if (params.delivery === 'rtlayer' && !params.threadId) {
|
|
313
|
+
throw new errors_js_1.ViaSocketError('ai.send: threadId is required with delivery "rtlayer" — subscribe to its channel, then send', { status: null });
|
|
314
|
+
}
|
|
315
|
+
if (params.prompt !== undefined)
|
|
316
|
+
body.prompt = params.prompt;
|
|
317
|
+
if (params.responseType)
|
|
318
|
+
body.response_type = params.responseType;
|
|
319
|
+
if (params.jsonSchema) {
|
|
320
|
+
if (params.responseType !== 'json_schema') {
|
|
321
|
+
throw new errors_js_1.ViaSocketError('ai.send: jsonSchema is only allowed with responseType "json_schema"', { status: null });
|
|
322
|
+
}
|
|
323
|
+
body.json_schema = params.jsonSchema;
|
|
324
|
+
}
|
|
325
|
+
if (!params.model !== !params.service)
|
|
326
|
+
throw new errors_js_1.ViaSocketError('ai.send: model and service go together', { status: null });
|
|
327
|
+
if (params.model) {
|
|
328
|
+
body.model = params.model;
|
|
329
|
+
body.service = params.service;
|
|
330
|
+
}
|
|
331
|
+
if (params.tools?.length) {
|
|
332
|
+
if (params.tools.length > AI_MAX_TOOLS)
|
|
333
|
+
throw new errors_js_1.ViaSocketError(`ai.send: at most ${AI_MAX_TOOLS} tools in one call`, { status: null });
|
|
334
|
+
body.extra_tools = params.tools.map((tool) => ({
|
|
335
|
+
name: required('tool.name', tool?.name),
|
|
336
|
+
description: required('tool.description', tool?.description),
|
|
337
|
+
url: required('tool.url', tool?.url),
|
|
338
|
+
method: tool.method ?? 'POST',
|
|
339
|
+
headers: tool.headers ?? {},
|
|
340
|
+
fields: tool.fields ?? {},
|
|
341
|
+
required_params: tool.requiredParams ?? [],
|
|
342
|
+
tool_and_variable_path: tool.toolAndVariablePath ?? {}
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
if (params.delivery)
|
|
346
|
+
body.delivery = params.delivery;
|
|
347
|
+
const { data } = await this.call('POST', '/embed/ai/message', body);
|
|
348
|
+
if (params.delivery === 'rtlayer')
|
|
349
|
+
return { threadId: data.thread_id, messageId: data.message_id, channel: data.channel ?? '' };
|
|
350
|
+
return { threadId: data.thread_id, messageId: data.message_id, content: data.content ?? '', finishReason: data.finish_reason ?? '' };
|
|
351
|
+
}),
|
|
352
|
+
/** This user's threads in this project, most recently active first. */
|
|
353
|
+
threads: async () => {
|
|
354
|
+
const { data } = await this.call('GET', '/embed/ai/threads');
|
|
355
|
+
return (data?.threads ?? []).map((thread) => ({ threadId: thread.thread_id, title: thread.title ?? null, updatedAt: thread.updated_at }));
|
|
356
|
+
},
|
|
357
|
+
/** One thread's messages, 40 exchanges a page; page 1 holds the latest, oldest of those first. */
|
|
358
|
+
history: async (threadId, params = {}) => {
|
|
359
|
+
const page = Math.max(1, Math.floor(params.page ?? 1));
|
|
360
|
+
const { data } = await this.call('GET', `/embed/ai/threads/${encodeURIComponent(required('threadId', threadId))}/history?page=${page}`);
|
|
361
|
+
return {
|
|
362
|
+
threadId: data.thread_id ?? threadId,
|
|
363
|
+
messages: (data.messages ?? []).map((message) => ({
|
|
364
|
+
role: message.role,
|
|
365
|
+
content: message.content,
|
|
366
|
+
messageId: message.message_id,
|
|
367
|
+
createdAt: message.created_at
|
|
368
|
+
})),
|
|
369
|
+
page: data.page ?? page,
|
|
370
|
+
hasMore: Boolean(data.has_more)
|
|
371
|
+
};
|
|
372
|
+
},
|
|
373
|
+
/** A 48-hour subscribe token for the browser, and the prefix every thread's channel starts with. */
|
|
374
|
+
rtlayerToken: async () => {
|
|
375
|
+
const { data } = await this.call('GET', '/embed/ai/rtlayer-token');
|
|
376
|
+
return { token: data.token, orgId: data.org_id, serviceId: data.service_id, channelPrefix: data.channel_prefix, expiresIn: data.expires_in };
|
|
377
|
+
}
|
|
378
|
+
};
|
|
288
379
|
/**
|
|
289
380
|
* Every app this user has connected. Returned as the API sends it: the shape of each entry
|
|
290
381
|
* is not yet documented, so nothing is mapped or dropped here.
|
package/dist/cjs/index.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* const { options } = await user.listOptions(actionVersionId, {...}) // fill a dropdown
|
|
13
13
|
* const result = await viasocket.runAction(scriptId, actionVersionId, inputData)
|
|
14
14
|
* const sub = await user.subscribe(triggerVersionId, { authId, inputData, code: handler })
|
|
15
|
+
* const reply = await user.ai.send({ threadId, message, prompt, tools }) // a chat assistant we run for this user
|
|
15
16
|
*
|
|
16
17
|
* The browser half — opening the connect popup — lives at 'viasocket-apps/browser'.
|
|
17
18
|
*/
|
package/dist/esm/browser.d.ts
CHANGED
|
@@ -50,6 +50,12 @@ export interface ConnectOptions {
|
|
|
50
50
|
actions?: string[];
|
|
51
51
|
/** With `actions`: skip the action list and open the app's consent screen directly. */
|
|
52
52
|
skipActionSelection?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* An existing connection's `authId`, to sign in to it again — when it has expired (`isExpired` in
|
|
55
|
+
* `user.listConnections()`), or to grant scopes it lacks. The popup updates that connection in
|
|
56
|
+
* place and resolves with the same `authId`, so nothing stored against it changes.
|
|
57
|
+
*/
|
|
58
|
+
authId?: string;
|
|
53
59
|
/**
|
|
54
60
|
* Called if the connection completes after `connect()` already rejected with `'closed'` — the
|
|
55
61
|
* close was inferred while the user was still on the app's sign-in page. Treat it exactly like
|
|
@@ -67,6 +73,7 @@ export interface ConnectOptions {
|
|
|
67
73
|
interface ConnectScriptOptions {
|
|
68
74
|
filteredActions?: string[];
|
|
69
75
|
skipActionSelection?: boolean;
|
|
76
|
+
authId?: string;
|
|
70
77
|
}
|
|
71
78
|
export interface ConnectResult {
|
|
72
79
|
/** The connection. Every later call for this app takes it as auth_id. */
|
package/dist/esm/browser.js
CHANGED
|
@@ -138,6 +138,8 @@ export async function connect(options) {
|
|
|
138
138
|
scriptOptions.filteredActions = options.actions;
|
|
139
139
|
if (options.skipActionSelection)
|
|
140
140
|
scriptOptions.skipActionSelection = true;
|
|
141
|
+
if (options.authId)
|
|
142
|
+
scriptOptions.authId = options.authId;
|
|
141
143
|
if (Object.keys(scriptOptions).length)
|
|
142
144
|
open(options.embedToken, serviceId, scriptOptions);
|
|
143
145
|
else
|
package/dist/esm/client.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* everything else behind `user(id)` makes that distinction structural rather than something a
|
|
8
8
|
* reader has to remember.
|
|
9
9
|
*/
|
|
10
|
-
import type { CatalogApp, CatalogVersions, EnableResult, Flow, FlowStatus, ListOptionsParams, ListAppsParams, ListOptionsResult, SubscribeParams, SubscribeResult, UpdateSubscriptionParams, ViaSocketOptions } from './types.js';
|
|
10
|
+
import type { AiHistoryPage, AiRtLayerToken, AiSend, AiThread, CatalogApp, CatalogVersions, EnableResult, Flow, FlowStatus, ListOptionsParams, ListAppsParams, ListOptionsResult, SubscribeParams, SubscribeResult, UpdateSubscriptionParams, ViaSocketOptions } from './types.js';
|
|
11
11
|
export declare class ViaSocket {
|
|
12
12
|
readonly orgId: string;
|
|
13
13
|
readonly projectId: string;
|
|
@@ -108,6 +108,29 @@ export declare class UserScope {
|
|
|
108
108
|
id: string;
|
|
109
109
|
status: string;
|
|
110
110
|
}>;
|
|
111
|
+
/**
|
|
112
|
+
* The AI API: a chat assistant viaSocket runs for this user — threads and history kept per
|
|
113
|
+
* `unique_identifier`, your prompt per call, your own endpoints as its tools. Backend only, like
|
|
114
|
+
* everything else here: the prompt and the tools' headers are yours to keep off the browser.
|
|
115
|
+
* The whole contract is https://flow.viasocket.com/documentation/ai.md.
|
|
116
|
+
*/
|
|
117
|
+
readonly ai: {
|
|
118
|
+
/**
|
|
119
|
+
* Sends one message and returns the answer — or, with `delivery: 'rtlayer'`, the 202's
|
|
120
|
+
* `channel` the answer will be published on (subscribe to it in the browser before sending).
|
|
121
|
+
* Refuses, before any request, what the API would refuse: a threadId with other characters,
|
|
122
|
+
* `rtlayer` without a threadId, a model without its service, a jsonSchema without its responseType.
|
|
123
|
+
*/
|
|
124
|
+
send: AiSend;
|
|
125
|
+
/** This user's threads in this project, most recently active first. */
|
|
126
|
+
threads: () => Promise<AiThread[]>;
|
|
127
|
+
/** One thread's messages, 40 exchanges a page; page 1 holds the latest, oldest of those first. */
|
|
128
|
+
history: (threadId: string, params?: {
|
|
129
|
+
page?: number;
|
|
130
|
+
}) => Promise<AiHistoryPage>;
|
|
131
|
+
/** A 48-hour subscribe token for the browser, and the prefix every thread's channel starts with. */
|
|
132
|
+
rtlayerToken: () => Promise<AiRtLayerToken>;
|
|
133
|
+
};
|
|
111
134
|
/**
|
|
112
135
|
* Every app this user has connected. Returned as the API sends it: the shape of each entry
|
|
113
136
|
* is not yet documented, so nothing is mapped or dropped here.
|
package/dist/esm/client.js
CHANGED
|
@@ -11,6 +11,9 @@ import { ViaSocketError } from './errors.js';
|
|
|
11
11
|
import { request } from './http.js';
|
|
12
12
|
import { signEmbedToken } from './token.js';
|
|
13
13
|
const DEFAULT_API_BASE_URL = 'https://flow-api.viasocket.com';
|
|
14
|
+
/** The AI API's thread id: the product's own string. */
|
|
15
|
+
const AI_THREAD_ID = /^[A-Za-z0-9_-]{1,100}$/;
|
|
16
|
+
const AI_MAX_TOOLS = 20;
|
|
14
17
|
const DEFAULT_RUN_BASE_URL = 'https://flow.sokt.io';
|
|
15
18
|
/** The public catalog: no token, the same data in every environment. */
|
|
16
19
|
const CATALOG = {
|
|
@@ -281,6 +284,94 @@ export class UserScope {
|
|
|
281
284
|
enableFlow(scriptId) {
|
|
282
285
|
return this.setFlowStatus(scriptId, 1);
|
|
283
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* The AI API: a chat assistant viaSocket runs for this user — threads and history kept per
|
|
289
|
+
* `unique_identifier`, your prompt per call, your own endpoints as its tools. Backend only, like
|
|
290
|
+
* everything else here: the prompt and the tools' headers are yours to keep off the browser.
|
|
291
|
+
* The whole contract is https://flow.viasocket.com/documentation/ai.md.
|
|
292
|
+
*/
|
|
293
|
+
ai = {
|
|
294
|
+
/**
|
|
295
|
+
* Sends one message and returns the answer — or, with `delivery: 'rtlayer'`, the 202's
|
|
296
|
+
* `channel` the answer will be published on (subscribe to it in the browser before sending).
|
|
297
|
+
* Refuses, before any request, what the API would refuse: a threadId with other characters,
|
|
298
|
+
* `rtlayer` without a threadId, a model without its service, a jsonSchema without its responseType.
|
|
299
|
+
*/
|
|
300
|
+
send: (async (params) => {
|
|
301
|
+
const body = { message: required('message', params?.message) };
|
|
302
|
+
if (params.threadId !== undefined) {
|
|
303
|
+
if (!AI_THREAD_ID.test(params.threadId)) {
|
|
304
|
+
throw new ViaSocketError('ai.send: threadId is letters, digits, - and _, up to 100 characters', { status: null });
|
|
305
|
+
}
|
|
306
|
+
body.thread_id = params.threadId;
|
|
307
|
+
}
|
|
308
|
+
if (params.delivery === 'rtlayer' && !params.threadId) {
|
|
309
|
+
throw new ViaSocketError('ai.send: threadId is required with delivery "rtlayer" — subscribe to its channel, then send', { status: null });
|
|
310
|
+
}
|
|
311
|
+
if (params.prompt !== undefined)
|
|
312
|
+
body.prompt = params.prompt;
|
|
313
|
+
if (params.responseType)
|
|
314
|
+
body.response_type = params.responseType;
|
|
315
|
+
if (params.jsonSchema) {
|
|
316
|
+
if (params.responseType !== 'json_schema') {
|
|
317
|
+
throw new ViaSocketError('ai.send: jsonSchema is only allowed with responseType "json_schema"', { status: null });
|
|
318
|
+
}
|
|
319
|
+
body.json_schema = params.jsonSchema;
|
|
320
|
+
}
|
|
321
|
+
if (!params.model !== !params.service)
|
|
322
|
+
throw new ViaSocketError('ai.send: model and service go together', { status: null });
|
|
323
|
+
if (params.model) {
|
|
324
|
+
body.model = params.model;
|
|
325
|
+
body.service = params.service;
|
|
326
|
+
}
|
|
327
|
+
if (params.tools?.length) {
|
|
328
|
+
if (params.tools.length > AI_MAX_TOOLS)
|
|
329
|
+
throw new ViaSocketError(`ai.send: at most ${AI_MAX_TOOLS} tools in one call`, { status: null });
|
|
330
|
+
body.extra_tools = params.tools.map((tool) => ({
|
|
331
|
+
name: required('tool.name', tool?.name),
|
|
332
|
+
description: required('tool.description', tool?.description),
|
|
333
|
+
url: required('tool.url', tool?.url),
|
|
334
|
+
method: tool.method ?? 'POST',
|
|
335
|
+
headers: tool.headers ?? {},
|
|
336
|
+
fields: tool.fields ?? {},
|
|
337
|
+
required_params: tool.requiredParams ?? [],
|
|
338
|
+
tool_and_variable_path: tool.toolAndVariablePath ?? {}
|
|
339
|
+
}));
|
|
340
|
+
}
|
|
341
|
+
if (params.delivery)
|
|
342
|
+
body.delivery = params.delivery;
|
|
343
|
+
const { data } = await this.call('POST', '/embed/ai/message', body);
|
|
344
|
+
if (params.delivery === 'rtlayer')
|
|
345
|
+
return { threadId: data.thread_id, messageId: data.message_id, channel: data.channel ?? '' };
|
|
346
|
+
return { threadId: data.thread_id, messageId: data.message_id, content: data.content ?? '', finishReason: data.finish_reason ?? '' };
|
|
347
|
+
}),
|
|
348
|
+
/** This user's threads in this project, most recently active first. */
|
|
349
|
+
threads: async () => {
|
|
350
|
+
const { data } = await this.call('GET', '/embed/ai/threads');
|
|
351
|
+
return (data?.threads ?? []).map((thread) => ({ threadId: thread.thread_id, title: thread.title ?? null, updatedAt: thread.updated_at }));
|
|
352
|
+
},
|
|
353
|
+
/** One thread's messages, 40 exchanges a page; page 1 holds the latest, oldest of those first. */
|
|
354
|
+
history: async (threadId, params = {}) => {
|
|
355
|
+
const page = Math.max(1, Math.floor(params.page ?? 1));
|
|
356
|
+
const { data } = await this.call('GET', `/embed/ai/threads/${encodeURIComponent(required('threadId', threadId))}/history?page=${page}`);
|
|
357
|
+
return {
|
|
358
|
+
threadId: data.thread_id ?? threadId,
|
|
359
|
+
messages: (data.messages ?? []).map((message) => ({
|
|
360
|
+
role: message.role,
|
|
361
|
+
content: message.content,
|
|
362
|
+
messageId: message.message_id,
|
|
363
|
+
createdAt: message.created_at
|
|
364
|
+
})),
|
|
365
|
+
page: data.page ?? page,
|
|
366
|
+
hasMore: Boolean(data.has_more)
|
|
367
|
+
};
|
|
368
|
+
},
|
|
369
|
+
/** A 48-hour subscribe token for the browser, and the prefix every thread's channel starts with. */
|
|
370
|
+
rtlayerToken: async () => {
|
|
371
|
+
const { data } = await this.call('GET', '/embed/ai/rtlayer-token');
|
|
372
|
+
return { token: data.token, orgId: data.org_id, serviceId: data.service_id, channelPrefix: data.channel_prefix, expiresIn: data.expires_in };
|
|
373
|
+
}
|
|
374
|
+
};
|
|
284
375
|
/**
|
|
285
376
|
* Every app this user has connected. Returned as the API sends it: the shape of each entry
|
|
286
377
|
* is not yet documented, so nothing is mapped or dropped here.
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* const { options } = await user.listOptions(actionVersionId, {...}) // fill a dropdown
|
|
12
12
|
* const result = await viasocket.runAction(scriptId, actionVersionId, inputData)
|
|
13
13
|
* const sub = await user.subscribe(triggerVersionId, { authId, inputData, code: handler })
|
|
14
|
+
* const reply = await user.ai.send({ threadId, message, prompt, tools }) // a chat assistant we run for this user
|
|
14
15
|
*
|
|
15
16
|
* The browser half — opening the connect popup — lives at 'viasocket-apps/browser'.
|
|
16
17
|
*/
|
|
@@ -18,4 +19,4 @@ export { ViaSocket, UserScope } from './client.js';
|
|
|
18
19
|
export { ViaSocketError } from './errors.js';
|
|
19
20
|
export { signEmbedToken } from './token.js';
|
|
20
21
|
export type { SignEmbedTokenInput } from './token.js';
|
|
21
|
-
export type { CatalogApp, CatalogAction, CatalogService, CatalogTrigger, CatalogVersion, CatalogVersions, EnableResult, Envelope, Flow, FlowStatus, ListAppsParams, ListOptionsParams, ListOptionsResult, Option, SubscribeParams, SubscribeResult, UpdateSubscriptionParams, ViaSocketOptions } from './types.js';
|
|
22
|
+
export type { AiAccepted, AiHistoryPage, AiMessage, AiReply, AiRtLayerToken, AiSend, AiSendParams, AiThread, AiTool, CatalogApp, CatalogAction, CatalogService, CatalogTrigger, CatalogVersion, CatalogVersions, EnableResult, Envelope, Flow, FlowStatus, ListAppsParams, ListOptionsParams, ListOptionsResult, Option, SubscribeParams, SubscribeResult, UpdateSubscriptionParams, ViaSocketOptions } from './types.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* const { options } = await user.listOptions(actionVersionId, {...}) // fill a dropdown
|
|
12
12
|
* const result = await viasocket.runAction(scriptId, actionVersionId, inputData)
|
|
13
13
|
* const sub = await user.subscribe(triggerVersionId, { authId, inputData, code: handler })
|
|
14
|
+
* const reply = await user.ai.send({ threadId, message, prompt, tools }) // a chat assistant we run for this user
|
|
14
15
|
*
|
|
15
16
|
* The browser half — opening the connect popup — lives at 'viasocket-apps/browser'.
|
|
16
17
|
*/
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -132,8 +132,10 @@ interface CatalogEventBase {
|
|
|
132
132
|
name: string;
|
|
133
133
|
description: string;
|
|
134
134
|
/**
|
|
135
|
-
* The field schema a form is rendered from; `inputData` follows it. A
|
|
136
|
-
*
|
|
135
|
+
* The field schema a form is rendered from; `inputData` follows it. A `dropdown` / `multiselect`
|
|
136
|
+
* block with no `options` (or with `enableSearchApi` / `willDynamicFetchOptions`), and an
|
|
137
|
+
* `input groups` block with no children in `steps`, is a picker: fill it with `listOptions`. The
|
|
138
|
+
* schema carries no generator code.
|
|
137
139
|
*/
|
|
138
140
|
input_schema: Record<string, unknown>;
|
|
139
141
|
/** A real response of the action, or a real event payload of the trigger. */
|
|
@@ -177,4 +179,113 @@ export interface Envelope<T = unknown> {
|
|
|
177
179
|
data: T;
|
|
178
180
|
isCached?: boolean;
|
|
179
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* The AI API — `user.ai`: a chat assistant viaSocket runs for one end user, with threads and
|
|
184
|
+
* history kept per `unique_identifier` and your own HTTP endpoints as the tools it may call.
|
|
185
|
+
*/
|
|
186
|
+
/** One HTTP endpoint of yours the assistant may call while answering. */
|
|
187
|
+
export interface AiTool {
|
|
188
|
+
/** The name the model calls it by, and the name you use in the prompt: letters, digits, underscores. */
|
|
189
|
+
name: string;
|
|
190
|
+
/** When to use it, in a sentence or two. The model decides from this. */
|
|
191
|
+
description: string;
|
|
192
|
+
/** The endpoint to call: your own backend, with its own auth in `headers`. Never a run URL — its script_id is a credential. */
|
|
193
|
+
url: string;
|
|
194
|
+
/** Default POST. */
|
|
195
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
196
|
+
/** Headers for that call — your own API key. They stay server-side. */
|
|
197
|
+
headers?: Record<string, string>;
|
|
198
|
+
/** The parameters the model may fill, each with a `type` and a `description` it fills from. */
|
|
199
|
+
fields?: Record<string, {
|
|
200
|
+
type: string;
|
|
201
|
+
description?: string;
|
|
202
|
+
[key: string]: unknown;
|
|
203
|
+
}>;
|
|
204
|
+
/** Which of `fields` the model must fill before calling. */
|
|
205
|
+
requiredParams?: string[];
|
|
206
|
+
/** Maps prompt variables to tool parameters. Advanced; omit for an ordinary tool. */
|
|
207
|
+
toolAndVariablePath?: Record<string, unknown>;
|
|
208
|
+
}
|
|
209
|
+
export interface AiSendParams {
|
|
210
|
+
/** What the user said. */
|
|
211
|
+
message: string;
|
|
212
|
+
/**
|
|
213
|
+
* Your own string per conversation: letters, digits, `-` and `_`, up to 100 characters. Omit to
|
|
214
|
+
* start a thread and read the id from the reply. Required with `delivery: 'rtlayer'`, so the
|
|
215
|
+
* browser can subscribe to the thread's channel before the send.
|
|
216
|
+
*/
|
|
217
|
+
threadId?: string;
|
|
218
|
+
/**
|
|
219
|
+
* The system prompt for this call. It is not stored, so send it every time. May use
|
|
220
|
+
* `{{orgId}}`, `{{projectId}}` and `{{userId}}`. Name the tools in it and say when to use each.
|
|
221
|
+
*/
|
|
222
|
+
prompt?: string;
|
|
223
|
+
/** `text` (default); `json_object`; or `json_schema`, which needs `jsonSchema`. */
|
|
224
|
+
responseType?: 'text' | 'json_object' | 'json_schema';
|
|
225
|
+
jsonSchema?: {
|
|
226
|
+
name: string;
|
|
227
|
+
schema: Record<string, unknown>;
|
|
228
|
+
strict?: boolean;
|
|
229
|
+
};
|
|
230
|
+
/** A model and its provider, always together: `model: 'gpt-5.6-luna', service: 'openai'`. Omit both for the default. */
|
|
231
|
+
model?: string;
|
|
232
|
+
service?: string;
|
|
233
|
+
/** Up to 20. Only the ones this conversation can need. */
|
|
234
|
+
tools?: AiTool[];
|
|
235
|
+
/** `sync` (default): the answer in the response. `rtlayer`: a 202 and the answer on the thread's websocket channel. */
|
|
236
|
+
delivery?: 'sync' | 'rtlayer';
|
|
237
|
+
}
|
|
238
|
+
/** The answer, from a synchronous send. */
|
|
239
|
+
export interface AiReply {
|
|
240
|
+
threadId: string;
|
|
241
|
+
messageId: string;
|
|
242
|
+
/** A string, or the parsed object for a JSON `responseType`. */
|
|
243
|
+
content: string | Record<string, unknown>;
|
|
244
|
+
/** `completed`, `truncated` (out of output tokens), or `tool_call` (stopped to call a tool). */
|
|
245
|
+
finishReason: 'completed' | 'truncated' | 'tool_call' | string;
|
|
246
|
+
}
|
|
247
|
+
/** The acknowledgement of a send with `delivery: 'rtlayer'`; the answer follows on `channel`. */
|
|
248
|
+
export interface AiAccepted {
|
|
249
|
+
threadId: string;
|
|
250
|
+
messageId: string;
|
|
251
|
+
/** `channelPrefix + threadId`, from `rtlayerToken()`. */
|
|
252
|
+
channel: string;
|
|
253
|
+
}
|
|
254
|
+
export interface AiSend {
|
|
255
|
+
(params: AiSendParams & {
|
|
256
|
+
delivery: 'rtlayer';
|
|
257
|
+
}): Promise<AiAccepted>;
|
|
258
|
+
(params: AiSendParams & {
|
|
259
|
+
delivery?: 'sync';
|
|
260
|
+
}): Promise<AiReply>;
|
|
261
|
+
}
|
|
262
|
+
export interface AiThread {
|
|
263
|
+
threadId: string;
|
|
264
|
+
/** Written by the AI from the thread's first message; `null` until then. */
|
|
265
|
+
title: string | null;
|
|
266
|
+
updatedAt: string;
|
|
267
|
+
}
|
|
268
|
+
export interface AiMessage {
|
|
269
|
+
role: 'user' | 'assistant' | string;
|
|
270
|
+
content: string;
|
|
271
|
+
/** A user message and its reply share one id. */
|
|
272
|
+
messageId: string;
|
|
273
|
+
createdAt: string;
|
|
274
|
+
}
|
|
275
|
+
/** 40 exchanges a page; page 1 holds the latest, in chronological order. */
|
|
276
|
+
export interface AiHistoryPage {
|
|
277
|
+
threadId: string;
|
|
278
|
+
messages: AiMessage[];
|
|
279
|
+
page: number;
|
|
280
|
+
hasMore: boolean;
|
|
281
|
+
}
|
|
282
|
+
/** What the browser needs to listen for replies: valid 48 hours, this user's threads only. */
|
|
283
|
+
export interface AiRtLayerToken {
|
|
284
|
+
token: string;
|
|
285
|
+
orgId: string;
|
|
286
|
+
serviceId: string;
|
|
287
|
+
/** A thread's channel is this followed by the threadId. */
|
|
288
|
+
channelPrefix: string;
|
|
289
|
+
expiresIn: string;
|
|
290
|
+
}
|
|
180
291
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viasocket-apps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Server-side client for the viaSocket Apps API: let your users connect Slack, GitHub, Google Sheets or any of 2,300+ apps from your own product, then run actions and receive events through one API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"viasocket",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"sideEffects": false,
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node scripts/finish-build.mjs",
|
|
50
|
-
"test": "npm run build && node --test test/token.test.mjs test/client.test.mjs",
|
|
50
|
+
"test": "npm run build && node --test test/token.test.mjs test/client.test.mjs test/browser.test.mjs",
|
|
51
51
|
"prepublishOnly": "npm test"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|