@ziggs-ai/api-client 0.25.0 → 0.26.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/dist/capabilities/chat.js +37 -17
- package/dist/capabilities/nextCall.js +14 -0
- package/dist/http/ChatClient.d.ts +15 -5
- package/dist/http/ChatClient.js +9 -10
- package/package.json +1 -1
|
@@ -51,20 +51,22 @@ export const openConversationCapability = {
|
|
|
51
51
|
names: { sdk: 'chat_open', mcp: 'ziggs_chat_open' },
|
|
52
52
|
title: 'Start or reuse a conversation',
|
|
53
53
|
descriptions: {
|
|
54
|
-
sdk: 'Open or
|
|
55
|
-
mcp: 'Open or
|
|
54
|
+
sdk: 'Open a conversation or explicitly invite people to a room. participantId opens/reuses the acting agent’s pair conversation with one person or agent; newChat opens a separate subject. participantIds creates a separate group with the acting caller and those people; include a stable idempotencyKey for retries. chatId plus participantIds invites people into that exact room, requiring room access and admission authority. Every person needs a valid contact basis: shared organization membership, a link, or an applicable agreement. Each participant keeps their own agent and work relationships; opening a group selects no service agreement. Group creation never expands an existing private DM. Invited teammates read from admission onward. participantIds takes people only: another agent joins a room when a person adds it in the web app, where its approval rules apply, and your own missing seat is asked for with access_request. Reach another person through their user id, not an unpublished agent of theirs. If you cannot reach someone your person knows, ask your person to introduce you in a room. Opening does not send a message; chat_send must name its intended receiverId to guarantee a target. To list accessible rooms, use grant_list scopeKind=chat.',
|
|
55
|
+
mcp: 'Open a conversation or explicitly invite people to a room. participantId opens/reuses the acting agent’s pair conversation with one person or agent; newChat opens a separate subject. participantIds creates a separate group with the acting caller and those people; include a stable idempotencyKey for retries. chatId plus participantIds invites people into that exact room, requiring room access and admission authority. Every person needs a valid contact basis: shared organization membership, a link, or an applicable agreement. Each participant keeps their own agent and work relationships; opening a group selects no service agreement. Group creation never expands an existing private DM. Invited teammates read from admission onward. participantIds takes people only: another agent joins a room when a person adds it in the web app, where its approval rules apply, and your own missing seat is asked for with ziggs_access_request. Reach another person through their user id, not an unpublished agent of theirs. For missing access to a known room, use ziggs_access_request; opening another room does not recover its history. Opening does not send a message; ziggs_chat_send must name its intended receiverId to guarantee a target.',
|
|
56
56
|
},
|
|
57
57
|
annotation: 'write',
|
|
58
58
|
params: {
|
|
59
59
|
participantId: {
|
|
60
60
|
type: 'string',
|
|
61
|
-
required: true,
|
|
62
61
|
description: 'Known user or agent id from search, a directory, a chat, or an agreement. For a teammate or a linked person, their user id, not an agent of theirs. No existing chat is required; do not guess ids.',
|
|
63
62
|
},
|
|
63
|
+
participantIds: { type: 'array', items: { type: 'string' }, description: '1–20 known user ids. Creates a separate group, or invites these people to chatId. Use instead of participantId. Each invitation is authorized separately.' },
|
|
64
|
+
chatId: { type: 'string', description: 'Existing room to invite participantIds into. Never creates another room. Omit creation options and agreementId.' },
|
|
65
|
+
idempotencyKey: { type: 'string', description: 'Required for group creation: a unique key for this conversation, reused unchanged on retries. A different participant set needs a new key.' },
|
|
64
66
|
createIfMissing: { type: 'boolean', description: 'Set false to reuse only an existing room for this acting agent and participant. A missing room is refused without creating a replacement.' },
|
|
65
67
|
newChat: {
|
|
66
68
|
type: 'boolean',
|
|
67
|
-
description: 'Open a separate chat even though one is already open with
|
|
69
|
+
description: 'Open a separate chat even though one is already open with the named participants. ' +
|
|
68
70
|
'For when the conversation is genuinely its own subject and would confuse an ' +
|
|
69
71
|
'existing thread. The separate chat does not become the main one, so a later ' +
|
|
70
72
|
'call without this still returns the original. Leave unset to continue where ' +
|
|
@@ -78,15 +80,31 @@ export const openConversationCapability = {
|
|
|
78
80
|
},
|
|
79
81
|
needsAgentId: true,
|
|
80
82
|
handler: async (args, env) => {
|
|
81
|
-
if (!args
|
|
82
|
-
throw new Error('participantId is required');
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
if (!args.participantId && !Array.isArray(args.participantIds)) {
|
|
84
|
+
throw new Error('participantId is required, or use participantIds to invite people');
|
|
85
|
+
}
|
|
86
|
+
if (args.participantIds !== undefined) {
|
|
87
|
+
if (args.participantId !== undefined || !Array.isArray(args.participantIds) ||
|
|
88
|
+
args.participantIds.length < 1 || args.participantIds.length > 20 ||
|
|
89
|
+
args.participantIds.some(id => typeof id !== 'string' || !id.trim())) {
|
|
90
|
+
throw new Error('Use participantIds containing 1–20 people, without participantId');
|
|
91
|
+
}
|
|
92
|
+
if (!args.chatId && (typeof args.idempotencyKey !== 'string' || !args.idempotencyKey.trim())) {
|
|
93
|
+
throw new Error('Group creation requires an idempotencyKey; reuse it when retrying');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (args.chatId || args.idempotencyKey) {
|
|
97
|
+
throw new Error('chatId and idempotencyKey require participantIds');
|
|
98
|
+
}
|
|
99
|
+
const { chatId, reused } = await openConversation({
|
|
100
|
+
...(typeof args.participantId === 'string' ? { participantId: args.participantId } : {}),
|
|
101
|
+
...(Array.isArray(args.participantIds) ? { participantIds: args.participantIds } : {}),
|
|
102
|
+
...(typeof args.chatId === 'string' ? { chatId: args.chatId } : {}),
|
|
103
|
+
...(typeof args.idempotencyKey === 'string' ? { idempotencyKey: args.idempotencyKey } : {}),
|
|
104
|
+
...(args.newChat === true ? { newChat: true } : {}),
|
|
85
105
|
...(typeof args.createIfMissing === 'boolean' ? { createIfMissing: args.createIfMissing } : {}),
|
|
86
|
-
...(typeof args
|
|
87
|
-
|
|
88
|
-
: {}),
|
|
89
|
-
});
|
|
106
|
+
...(typeof args.agreementId === 'string' && args.agreementId ? { agreementId: args.agreementId } : {}),
|
|
107
|
+
}, fullCreds(env));
|
|
90
108
|
const lister = env.surface === 'mcp' ? 'ziggs_grant_list' : 'grant_list';
|
|
91
109
|
const grantNote = `Use ${lister} scopeKind=chat to inspect your room grants. Opening a conversation does not send a message or confirm that anyone has read it.`;
|
|
92
110
|
// word the note from the outcome instead of covering both cases.
|
|
@@ -94,11 +112,13 @@ export const openConversationCapability = {
|
|
|
94
112
|
// withheld it — worse than silence, because the agent cannot even tell
|
|
95
113
|
// there is something to look up. A backend too old to report it keeps the
|
|
96
114
|
// old both-cases wording, and `reused` is simply absent from the result.
|
|
97
|
-
const outcomeNote =
|
|
98
|
-
? '
|
|
99
|
-
: reused ===
|
|
100
|
-
? '
|
|
101
|
-
:
|
|
115
|
+
const outcomeNote = args.chatId
|
|
116
|
+
? 'The named people can participate in this room. Their history access remains bounded by their grants.'
|
|
117
|
+
: reused === true
|
|
118
|
+
? 'Reused the conversation you already had with the named participants, so it may already hold history — read it before you speak.'
|
|
119
|
+
: reused === false
|
|
120
|
+
? 'Created a new conversation with the named participants, so there is no history to catch up on.'
|
|
121
|
+
: 'Conversation is open (or reused).';
|
|
102
122
|
return {
|
|
103
123
|
chatId,
|
|
104
124
|
actingAgentId: fullCreds(env).agentId,
|
|
@@ -216,6 +216,11 @@ export function nextCall(env, capabilityKey, args, why, opts) {
|
|
|
216
216
|
for (const [key, value] of Object.entries(args ?? {})) {
|
|
217
217
|
if (!isFilled(value))
|
|
218
218
|
continue;
|
|
219
|
+
if (key === 'participantIds' && Array.isArray(value) && value.some(isPersonaFace)) {
|
|
220
|
+
missing.push(key);
|
|
221
|
+
strippedFaces.push(key);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
219
224
|
if (ADDRESS_FIELDS.includes(key) &&
|
|
220
225
|
isPersonaFace(value)) {
|
|
221
226
|
missing.push(key);
|
|
@@ -225,11 +230,20 @@ export function nextCall(env, capabilityKey, args, why, opts) {
|
|
|
225
230
|
filled[key] = value;
|
|
226
231
|
}
|
|
227
232
|
for (const name of requiredArgNames(capabilityKey, opts?.definition)) {
|
|
233
|
+
if (capabilityKey === 'chat_open' && name === 'participantId' && Array.isArray(filled.participantIds) && filled.participantIds.length > 0)
|
|
234
|
+
continue;
|
|
228
235
|
if (!isFilled(filled[name])) {
|
|
229
236
|
if (!missing.includes(name))
|
|
230
237
|
missing.push(name);
|
|
231
238
|
}
|
|
232
239
|
}
|
|
240
|
+
if (capabilityKey === 'chat_open' && (!opts?.definition || 'participantIds' in opts.definition.params)) {
|
|
241
|
+
if (!filled.participantId && !(Array.isArray(filled.participantIds) && filled.participantIds.length > 0) && !missing.includes('participantId')) {
|
|
242
|
+
missing.push('participantId');
|
|
243
|
+
}
|
|
244
|
+
if (Array.isArray(filled.participantIds) && !filled.chatId && !filled.idempotencyKey)
|
|
245
|
+
missing.push('idempotencyKey');
|
|
246
|
+
}
|
|
233
247
|
const hold = opts?.hold;
|
|
234
248
|
const ready = missing.length === 0 && !hold;
|
|
235
249
|
const outcome = opts?.outcome ??
|
|
@@ -13,7 +13,16 @@ import { type Creds } from '../types.js';
|
|
|
13
13
|
* with without a second call.
|
|
14
14
|
*/
|
|
15
15
|
export type ChatSummary = ChatReadDto;
|
|
16
|
-
export
|
|
16
|
+
export interface OpenConversationInput {
|
|
17
|
+
participantId?: string;
|
|
18
|
+
participantIds?: string[];
|
|
19
|
+
chatId?: string;
|
|
20
|
+
idempotencyKey?: string;
|
|
21
|
+
newChat?: boolean;
|
|
22
|
+
agreementId?: string;
|
|
23
|
+
createIfMissing?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function openConversation(participant: string | OpenConversationInput, creds: Creds, { newChat, agreementId, createIfMissing }?: {
|
|
17
26
|
newChat?: boolean;
|
|
18
27
|
agreementId?: string;
|
|
19
28
|
createIfMissing?: boolean;
|
|
@@ -34,9 +43,10 @@ export interface SendChatMessageInput {
|
|
|
34
43
|
*/
|
|
35
44
|
to?: string;
|
|
36
45
|
/**
|
|
37
|
-
* Recipient id. Optional: when omitted, the
|
|
38
|
-
* receiver
|
|
39
|
-
*
|
|
46
|
+
* Recipient id. Optional: when omitted, the one other participant in the
|
|
47
|
+
* room is the receiver; with several, the backend infers one when Jev is
|
|
48
|
+
* confident. Otherwise it stores room context without a specific wake.
|
|
49
|
+
* Explicit addressing is required for a guaranteed target.
|
|
40
50
|
*/
|
|
41
51
|
receiverId?: string;
|
|
42
52
|
text: string;
|
|
@@ -118,7 +128,7 @@ export declare class ChatClient {
|
|
|
118
128
|
* @param agentId Required for fleet keys (sent as X-Agent-Id). Omit for agent-scoped keys.
|
|
119
129
|
*/
|
|
120
130
|
constructor(operatorKey: string, agentId?: string);
|
|
121
|
-
open(
|
|
131
|
+
open(participant: string | OpenConversationInput, opts?: {
|
|
122
132
|
newChat?: boolean;
|
|
123
133
|
agreementId?: string;
|
|
124
134
|
createIfMissing?: boolean;
|
package/dist/http/ChatClient.js
CHANGED
|
@@ -10,21 +10,20 @@ function assertCreds(creds, op) {
|
|
|
10
10
|
if (!creds?.agentId)
|
|
11
11
|
throw new Error(`agentId is required for ${op}`);
|
|
12
12
|
}
|
|
13
|
-
export async function openConversation(
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
export async function openConversation(participant, creds, { newChat = false, agreementId, createIfMissing } = {}) {
|
|
14
|
+
const input = typeof participant === 'string'
|
|
15
|
+
? { participantId: participant, ...(newChat ? { newChat } : {}), ...(agreementId ? { agreementId } : {}), ...(createIfMissing !== undefined ? { createIfMissing } : {}) }
|
|
16
|
+
: participant;
|
|
17
|
+
if (!input || (!input.participantId && !input.participantIds?.length)) {
|
|
18
|
+
throw new Error('participantId is required, or use participantIds to invite people');
|
|
19
|
+
}
|
|
16
20
|
assertCreds(creds, 'open conversation');
|
|
17
21
|
const res = await fetch(`${getBackendUrl()}/chats`, {
|
|
18
22
|
method: 'POST',
|
|
19
23
|
headers: buildHeaders(creds),
|
|
20
24
|
// Only sent when asked for: an older backend ignores the field, so a
|
|
21
25
|
// caller that never wants a separate room behaves identically either way.
|
|
22
|
-
body: JSON.stringify(
|
|
23
|
-
participantId,
|
|
24
|
-
...(newChat ? { newChat: true } : {}),
|
|
25
|
-
...(createIfMissing !== undefined ? { createIfMissing } : {}),
|
|
26
|
-
...(agreementId ? { agreementId } : {}),
|
|
27
|
-
}),
|
|
26
|
+
body: JSON.stringify(input),
|
|
28
27
|
});
|
|
29
28
|
if (!res.ok) {
|
|
30
29
|
const body = await res.text().catch(() => '');
|
|
@@ -166,7 +165,7 @@ export class ChatClient {
|
|
|
166
165
|
// standalone functions still assert it per call.
|
|
167
166
|
this.creds = { operatorKey, agentId };
|
|
168
167
|
}
|
|
169
|
-
open(
|
|
168
|
+
open(participant, opts) { return openConversation(participant, this.creds, opts); }
|
|
170
169
|
addMember(input) { return addChatMember(input, this.creds); }
|
|
171
170
|
sendMessage(input) { return sendChatMessage(input, this.creds); }
|
|
172
171
|
listMine() { return listMyChats(this.creds); }
|