@ziggs-ai/ziggs-mcp 0.37.3 → 0.37.4
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/inboxCompact.d.ts +49 -5
- package/dist/inboxCompact.js +158 -21
- package/dist/tools.js +9 -4
- package/dist/workspaceRouting.js +2 -2
- package/package.json +2 -2
package/dist/inboxCompact.d.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Every assistant start-up pays for one inbox read, and it was several times
|
|
5
5
|
* the size of the connect text before any content was opened. This changes how
|
|
6
6
|
* the result is written, never what it says: which deliveries are visible,
|
|
7
|
-
* their order, their reach, the acknowledgement tokens, the
|
|
8
|
-
* lease all come from the server unchanged. What goes is only
|
|
9
|
-
* back from what stays:
|
|
7
|
+
* their order within a thread, their reach, the acknowledgement tokens, the
|
|
8
|
+
* counters and the lease all come from the server unchanged. What goes is only
|
|
9
|
+
* what can be read back from what stays:
|
|
10
10
|
*
|
|
11
11
|
* - a delivery field that is null, false or empty (absent reads the same);
|
|
12
12
|
* - `partyKind` on a delivery, which `sources` already gives for its `partyId`,
|
|
@@ -24,17 +24,61 @@
|
|
|
24
24
|
*
|
|
25
25
|
* Acknowledgement stays exact: every `ackTo` — the envelope's, each row's and
|
|
26
26
|
* the ack step's — is passed through byte for byte.
|
|
27
|
+
*
|
|
28
|
+
* The rows are written grouped rather than as one flat list: by who they are
|
|
29
|
+
* for, then by who sent them, then by the chat, task or agreement they are
|
|
30
|
+
* about. A flat list mixed the assistant's own work with mail it
|
|
31
|
+
* only passes along to its person and mail for the person's other
|
|
32
|
+
* assistants, and the split lived in one sentence of counts. Every row is
|
|
33
|
+
* still there, oldest first inside its thread; see {@link groupDeliveries}.
|
|
27
34
|
*/
|
|
28
35
|
type Json = Record<string, unknown>;
|
|
29
36
|
/** One delivery row, without what it would say by default or elsewhere. */
|
|
30
37
|
export declare function compactDelivery(row: Json, kindOfParty: Map<string, unknown>, usualPartyId?: unknown): Json;
|
|
38
|
+
/** Who an inbox read belongs to: the connection's assistants and their person. */
|
|
39
|
+
export interface InboxReader {
|
|
40
|
+
agentIds: readonly string[];
|
|
41
|
+
/** The person the assistants work for; read off the human source when absent. */
|
|
42
|
+
personId?: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** The sections of a grouped inbox, in the order they are written. */
|
|
45
|
+
export declare const INBOX_SECTIONS: readonly ["forYou", "forYourPerson", "forOtherAssistants", "context"];
|
|
46
|
+
export type InboxSection = (typeof INBOX_SECTIONS)[number];
|
|
47
|
+
/**
|
|
48
|
+
* Who a row is for. One rule for the grouping and for the counts in the
|
|
49
|
+
* notice, so the two cannot disagree: assigned to one of the reader's
|
|
50
|
+
* assistants is theirs to handle; unassigned and flagged for a human is the
|
|
51
|
+
* person's, which the assistant passes along; assigned to anyone else is
|
|
52
|
+
* another assistant's; the rest nobody is asked to act on.
|
|
53
|
+
*/
|
|
54
|
+
export declare function sectionOf(row: Json, agentIds: readonly string[]): InboxSection;
|
|
55
|
+
/**
|
|
56
|
+
* The window's rows, grouped: section, then sender, then thread.
|
|
57
|
+
*
|
|
58
|
+
* Nothing is hidden and nothing is reordered within a thread. Senders and
|
|
59
|
+
* threads are written in the order their oldest row arrived, which is the
|
|
60
|
+
* order the server sends (oldest first), except that the reader's own actions
|
|
61
|
+
* and their person's come last in a section: they are a record of what this
|
|
62
|
+
* side did, not news from anyone.
|
|
63
|
+
*
|
|
64
|
+
* A row drops only what its place in the grouping says: the sender (the
|
|
65
|
+
* group's `from`), the thread's own id, the assignee in `forYou` when the
|
|
66
|
+
* connection has one assistant, and `needsHuman` in `forYourPerson`. A thread
|
|
67
|
+
* names the first agreement its rows sit under; a row under another one keeps
|
|
68
|
+
* its own, and a row under none says `agreementId: null`, so every row reads
|
|
69
|
+
* back exactly.
|
|
70
|
+
*/
|
|
71
|
+
export declare function groupDeliveries(rows: readonly Json[], reader: InboxReader, compactRow?: (row: Json) => Json): Partial<Record<InboxSection, Json[]>>;
|
|
31
72
|
/**
|
|
32
73
|
* The compact inbox result. Takes the result exactly as it would otherwise be
|
|
33
74
|
* returned and gives back the same information in fewer characters.
|
|
75
|
+
*
|
|
76
|
+
* With a reader, `deliveries` is written grouped ({@link groupDeliveries});
|
|
77
|
+
* without one there is no telling whose a row is, so it stays one list.
|
|
34
78
|
*/
|
|
35
|
-
export declare function compactInboxResult(result: Json): Json;
|
|
79
|
+
export declare function compactInboxResult(result: Json, reader?: InboxReader): Json;
|
|
36
80
|
/** The inbox result as one line of JSON: indentation is most of nothing. */
|
|
37
|
-
export declare function compactTextResult(data: Json): {
|
|
81
|
+
export declare function compactTextResult(data: Json, reader?: InboxReader): {
|
|
38
82
|
content: {
|
|
39
83
|
type: "text";
|
|
40
84
|
text: string;
|
package/dist/inboxCompact.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Every assistant start-up pays for one inbox read, and it was several times
|
|
5
5
|
* the size of the connect text before any content was opened. This changes how
|
|
6
6
|
* the result is written, never what it says: which deliveries are visible,
|
|
7
|
-
* their order, their reach, the acknowledgement tokens, the
|
|
8
|
-
* lease all come from the server unchanged. What goes is only
|
|
9
|
-
* back from what stays:
|
|
7
|
+
* their order within a thread, their reach, the acknowledgement tokens, the
|
|
8
|
+
* counters and the lease all come from the server unchanged. What goes is only
|
|
9
|
+
* what can be read back from what stays:
|
|
10
10
|
*
|
|
11
11
|
* - a delivery field that is null, false or empty (absent reads the same);
|
|
12
12
|
* - `partyKind` on a delivery, which `sources` already gives for its `partyId`,
|
|
@@ -24,6 +24,13 @@
|
|
|
24
24
|
*
|
|
25
25
|
* Acknowledgement stays exact: every `ackTo` — the envelope's, each row's and
|
|
26
26
|
* the ack step's — is passed through byte for byte.
|
|
27
|
+
*
|
|
28
|
+
* The rows are written grouped rather than as one flat list: by who they are
|
|
29
|
+
* for, then by who sent them, then by the chat, task or agreement they are
|
|
30
|
+
* about. A flat list mixed the assistant's own work with mail it
|
|
31
|
+
* only passes along to its person and mail for the person's other
|
|
32
|
+
* assistants, and the split lived in one sentence of counts. Every row is
|
|
33
|
+
* still there, oldest first inside its thread; see {@link groupDeliveries}.
|
|
27
34
|
*/
|
|
28
35
|
const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
29
36
|
/** Session fields `ziggs_auth_status` carries; the inbox does not repeat them. */
|
|
@@ -84,11 +91,140 @@ function usualPartyOf(rows) {
|
|
|
84
91
|
[best, most] = [id, n];
|
|
85
92
|
return best;
|
|
86
93
|
}
|
|
94
|
+
/** The sections of a grouped inbox, in the order they are written. */
|
|
95
|
+
export const INBOX_SECTIONS = ['forYou', 'forYourPerson', 'forOtherAssistants', 'context'];
|
|
96
|
+
/**
|
|
97
|
+
* Who a row is for. One rule for the grouping and for the counts in the
|
|
98
|
+
* notice, so the two cannot disagree: assigned to one of the reader's
|
|
99
|
+
* assistants is theirs to handle; unassigned and flagged for a human is the
|
|
100
|
+
* person's, which the assistant passes along; assigned to anyone else is
|
|
101
|
+
* another assistant's; the rest nobody is asked to act on.
|
|
102
|
+
*/
|
|
103
|
+
export function sectionOf(row, agentIds) {
|
|
104
|
+
const assignee = row.assigneeId ? String(row.assigneeId) : '';
|
|
105
|
+
if (assignee && agentIds.includes(assignee))
|
|
106
|
+
return 'forYou';
|
|
107
|
+
if (!assignee && row.needsHuman === true)
|
|
108
|
+
return 'forYourPerson';
|
|
109
|
+
if (assignee)
|
|
110
|
+
return 'forOtherAssistants';
|
|
111
|
+
return 'context';
|
|
112
|
+
}
|
|
113
|
+
/** What a row is about: its chat, else its task, else its agreement, else itself. */
|
|
114
|
+
function anchorOf(row) {
|
|
115
|
+
if (row.chatId)
|
|
116
|
+
return ['chatId', String(row.chatId)];
|
|
117
|
+
if (row.taskId)
|
|
118
|
+
return ['taskId', String(row.taskId)];
|
|
119
|
+
if (row.agreementId)
|
|
120
|
+
return ['agreementId', String(row.agreementId)];
|
|
121
|
+
return [row.kind === 'artifact' ? 'artifactId' : 'resourceId', String(row.resourceId)];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The window's rows, grouped: section, then sender, then thread.
|
|
125
|
+
*
|
|
126
|
+
* Nothing is hidden and nothing is reordered within a thread. Senders and
|
|
127
|
+
* threads are written in the order their oldest row arrived, which is the
|
|
128
|
+
* order the server sends (oldest first), except that the reader's own actions
|
|
129
|
+
* and their person's come last in a section: they are a record of what this
|
|
130
|
+
* side did, not news from anyone.
|
|
131
|
+
*
|
|
132
|
+
* A row drops only what its place in the grouping says: the sender (the
|
|
133
|
+
* group's `from`), the thread's own id, the assignee in `forYou` when the
|
|
134
|
+
* connection has one assistant, and `needsHuman` in `forYourPerson`. A thread
|
|
135
|
+
* names the first agreement its rows sit under; a row under another one keeps
|
|
136
|
+
* its own, and a row under none says `agreementId: null`, so every row reads
|
|
137
|
+
* back exactly.
|
|
138
|
+
*/
|
|
139
|
+
export function groupDeliveries(rows, reader, compactRow = (row) => ({ ...row })) {
|
|
140
|
+
const sections = new Map();
|
|
141
|
+
for (const row of rows) {
|
|
142
|
+
const section = sectionOf(row, reader.agentIds);
|
|
143
|
+
const from = row.actorId ? String(row.actorId) : null;
|
|
144
|
+
const anchor = anchorOf(row);
|
|
145
|
+
let senders = sections.get(section);
|
|
146
|
+
if (!senders)
|
|
147
|
+
sections.set(section, (senders = new Map()));
|
|
148
|
+
let group = senders.get(String(from));
|
|
149
|
+
if (!group)
|
|
150
|
+
senders.set(String(from), (group = { from, threads: new Map() }));
|
|
151
|
+
const key = anchor.join(':');
|
|
152
|
+
let thread = group.threads.get(key);
|
|
153
|
+
if (!thread)
|
|
154
|
+
group.threads.set(key, (thread = { anchor, rows: [] }));
|
|
155
|
+
thread.rows.push(row);
|
|
156
|
+
}
|
|
157
|
+
const own = (from) => from !== null && (reader.agentIds.includes(from) || from === reader.personId);
|
|
158
|
+
const out = {};
|
|
159
|
+
for (const section of INBOX_SECTIONS) {
|
|
160
|
+
const senders = sections.get(section);
|
|
161
|
+
if (!senders)
|
|
162
|
+
continue;
|
|
163
|
+
const ordered = [...senders.values()];
|
|
164
|
+
out[section] = [
|
|
165
|
+
...ordered.filter((g) => !own(g.from)),
|
|
166
|
+
...ordered.filter((g) => own(g.from)),
|
|
167
|
+
].map((g) => {
|
|
168
|
+
const threads = [...g.threads.values()].map((t) => writeThread(t, section, reader, compactRow));
|
|
169
|
+
const count = [...g.threads.values()].reduce((n, t) => n + t.rows.length, 0);
|
|
170
|
+
return {
|
|
171
|
+
from: g.from,
|
|
172
|
+
...(g.from !== null && reader.agentIds.includes(g.from) ? { you: true } : {}),
|
|
173
|
+
...(g.from !== null && g.from === reader.personId ? { yourPerson: true } : {}),
|
|
174
|
+
count,
|
|
175
|
+
threads,
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
function writeThread(thread, section, reader, compactRow) {
|
|
182
|
+
const [field, id] = thread.anchor;
|
|
183
|
+
const lifted = field === 'agreementId'
|
|
184
|
+
? null
|
|
185
|
+
: thread.rows.map((r) => r.agreementId).find(Boolean) ?? null;
|
|
186
|
+
const rows = thread.rows.map((original) => {
|
|
187
|
+
const row = compactRow(original);
|
|
188
|
+
delete row.actorId;
|
|
189
|
+
if (field !== 'resourceId' && field !== 'artifactId')
|
|
190
|
+
delete row[field];
|
|
191
|
+
if (lifted) {
|
|
192
|
+
if (row.agreementId === lifted)
|
|
193
|
+
delete row.agreementId;
|
|
194
|
+
else if (!row.agreementId)
|
|
195
|
+
row.agreementId = null;
|
|
196
|
+
}
|
|
197
|
+
if (section === 'forYou') {
|
|
198
|
+
if (reader.agentIds.length === 1)
|
|
199
|
+
delete row.assigneeId;
|
|
200
|
+
if (row.assignedToConnection === true)
|
|
201
|
+
delete row.assignedToConnection;
|
|
202
|
+
}
|
|
203
|
+
if (section === 'forYourPerson')
|
|
204
|
+
delete row.needsHuman;
|
|
205
|
+
return row;
|
|
206
|
+
});
|
|
207
|
+
return { [field]: id, ...(lifted ? { agreementId: lifted } : {}), rows };
|
|
208
|
+
}
|
|
209
|
+
/** The reader's assistants, without empty ids, and their person. */
|
|
210
|
+
function readerOf(result, reader) {
|
|
211
|
+
const agentIds = (reader?.agentIds ?? []).filter(Boolean);
|
|
212
|
+
if (!agentIds.length)
|
|
213
|
+
return null;
|
|
214
|
+
const human = Array.isArray(result.sources)
|
|
215
|
+
? result.sources.find((s) => isRecord(s) && s.partyKind === 'human')
|
|
216
|
+
: undefined;
|
|
217
|
+
const personId = reader?.personId ?? (isRecord(human) ? String(human.partyId) : null);
|
|
218
|
+
return { agentIds, personId };
|
|
219
|
+
}
|
|
87
220
|
/**
|
|
88
221
|
* The compact inbox result. Takes the result exactly as it would otherwise be
|
|
89
222
|
* returned and gives back the same information in fewer characters.
|
|
223
|
+
*
|
|
224
|
+
* With a reader, `deliveries` is written grouped ({@link groupDeliveries});
|
|
225
|
+
* without one there is no telling whose a row is, so it stays one list.
|
|
90
226
|
*/
|
|
91
|
-
export function compactInboxResult(result) {
|
|
227
|
+
export function compactInboxResult(result, reader) {
|
|
92
228
|
const kindOfParty = new Map();
|
|
93
229
|
if (Array.isArray(result.sources))
|
|
94
230
|
for (const s of result.sources)
|
|
@@ -101,7 +237,10 @@ export function compactInboxResult(result) {
|
|
|
101
237
|
const usual = usualPartyOf(result.deliveries);
|
|
102
238
|
if (usual !== undefined)
|
|
103
239
|
out.deliveriesPartyId = usual;
|
|
104
|
-
|
|
240
|
+
const whose = readerOf(result, reader);
|
|
241
|
+
out.deliveries = whose
|
|
242
|
+
? groupDeliveries(result.deliveries.filter(isRecord), whose, (d) => compactDelivery(d, kindOfParty, usual))
|
|
243
|
+
: result.deliveries.map((d) => isRecord(d) ? compactDelivery(d, kindOfParty, usual) : d);
|
|
105
244
|
}
|
|
106
245
|
if (isRecord(result.session))
|
|
107
246
|
out.session = compactSession(result.session, result.connectionNotices);
|
|
@@ -135,9 +274,9 @@ export function compactInboxResult(result) {
|
|
|
135
274
|
return out;
|
|
136
275
|
}
|
|
137
276
|
/** The inbox result as one line of JSON: indentation is most of nothing. */
|
|
138
|
-
export function compactTextResult(data) {
|
|
277
|
+
export function compactTextResult(data, reader) {
|
|
139
278
|
return {
|
|
140
|
-
content: [{ type: 'text', text: JSON.stringify(compactInboxResult(data)) }],
|
|
279
|
+
content: [{ type: 'text', text: JSON.stringify(compactInboxResult(data, reader)) }],
|
|
141
280
|
};
|
|
142
281
|
}
|
|
143
282
|
/**
|
|
@@ -152,19 +291,17 @@ export function windowNote(deliveries, selfAgentIds, outOfReach) {
|
|
|
152
291
|
const rows = Array.isArray(deliveries) ? deliveries.filter(isRecord) : [];
|
|
153
292
|
if (!rows.length)
|
|
154
293
|
return '';
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
parts.push(`${others} ${others === 1 ? 'is' : 'are'} for other assistants`);
|
|
166
|
-
if (context)
|
|
167
|
-
parts.push(`${context} ${context === 1 ? 'is' : 'are'} context`);
|
|
294
|
+
// The same rule the grouping uses, so each count is its section's size.
|
|
295
|
+
const count = (s) => rows.filter((d) => sectionOf(d, selfAgentIds) === s).length;
|
|
296
|
+
const words = {
|
|
297
|
+
forYou: 'yours to handle',
|
|
298
|
+
forYourPerson: 'pass these along to your person',
|
|
299
|
+
forOtherAssistants: 'your person’s other assistants',
|
|
300
|
+
context: 'nobody is asked to act',
|
|
301
|
+
};
|
|
302
|
+
// forYou is always named, so "nothing for you" is said rather than implied.
|
|
303
|
+
const parts = INBOX_SECTIONS.filter((s) => s === 'forYou' || count(s) > 0).map((s) => `${s} ${count(s)} (${words[s]})`);
|
|
168
304
|
const reach = outOfReach ? ` ${outOfReach} of them ${outOfReach === 1 ? 'is' : 'are'} out of reach.` : '';
|
|
169
|
-
|
|
305
|
+
const total = `${rows.length} ${rows.length === 1 ? 'delivery' : 'deliveries'}`;
|
|
306
|
+
return `Those counts are decisions and tasks, not messages. This window has ${total}, grouped by who they are for, then by sender: ${parts.join(', ')}.${reach}`;
|
|
170
307
|
}
|
package/dist/tools.js
CHANGED
|
@@ -692,12 +692,14 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
692
692
|
}
|
|
693
693
|
}
|
|
694
694
|
const news = formatInboxToolResult(inbox, null, cfg.ZIGGS_WEB_URL, undefined, reach, undefined, { agentId: creds.agentId, ownerUserId: ownerPrincipalId(creds, cfg) });
|
|
695
|
+
// Whose rows are whose, so the deliveries come back grouped.
|
|
696
|
+
const reader = { agentIds: [creds.agentId], personId: ownerPrincipalId(creds, cfg) };
|
|
695
697
|
// A long-poll is a continuation of a session that already oriented
|
|
696
698
|
// itself, so it returns news only. A cold call is the session start, and
|
|
697
699
|
// carries what the other two orientation tools used to each be called
|
|
698
700
|
// for: the binding, the decisions, and the card.
|
|
699
701
|
if (waitSeconds != null)
|
|
700
|
-
return compactTextResult(news);
|
|
702
|
+
return compactTextResult(news, reader);
|
|
701
703
|
const [bindingSettled, readsSettled] = await sessionStart;
|
|
702
704
|
const session = bindingSettled.status === 'fulfilled'
|
|
703
705
|
? bindingSettled.value
|
|
@@ -712,7 +714,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
712
714
|
...news,
|
|
713
715
|
session,
|
|
714
716
|
sessionActionsFetchError: readsSettled.reason.message,
|
|
715
|
-
});
|
|
717
|
+
}, reader);
|
|
716
718
|
}
|
|
717
719
|
const actions = buildSessionActions(inbox, readsSettled.value, creds, cfg);
|
|
718
720
|
// No prose list of next actions rides here: the calls are in
|
|
@@ -729,7 +731,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
729
731
|
...(note && typeof actions.notice === 'string'
|
|
730
732
|
? { notice: `${actions.notice} ${note}` }
|
|
731
733
|
: {}),
|
|
732
|
-
});
|
|
734
|
+
}, reader);
|
|
733
735
|
}
|
|
734
736
|
catch (e) {
|
|
735
737
|
return toolError(e);
|
|
@@ -754,7 +756,10 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
754
756
|
registerStrictTool(server, 'ziggs_inbox_claim', {}, destructive('Claim the inbox for this worker'), async () => {
|
|
755
757
|
try {
|
|
756
758
|
const client = inboxFor(creds);
|
|
757
|
-
return compactTextResult(await client.getInbox()
|
|
759
|
+
return compactTextResult(await client.getInbox(), {
|
|
760
|
+
agentIds: [creds.agentId],
|
|
761
|
+
personId: ownerPrincipalId(creds, cfg),
|
|
762
|
+
});
|
|
758
763
|
}
|
|
759
764
|
catch (e) {
|
|
760
765
|
return toolError(e);
|
package/dist/workspaceRouting.js
CHANGED
|
@@ -235,8 +235,8 @@ export class WorkspaceRouter {
|
|
|
235
235
|
...(name === 'ziggs_inbox' && args.waitSeconds == null ? { rules: sessionRules('directory') } : {}),
|
|
236
236
|
...(note && typeof pending.notice === 'string' ? { notice: `${pending.notice} ${note}` } : {}),
|
|
237
237
|
session: { workspaces },
|
|
238
|
-
instruction: `
|
|
239
|
-
});
|
|
238
|
+
instruction: `deliveries.forYou is your work; deliveries.forYourPerson is for your person to see. ${ASK_YOUR_HUMAN}`,
|
|
239
|
+
}, { agentIds: self.agentIds ?? [] });
|
|
240
240
|
}
|
|
241
241
|
if (name === 'ziggs_inbox_peek') {
|
|
242
242
|
return result(await this.client.request(`inbox/peek?wait=${Number(args.waitSeconds ?? 0)}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.4",
|
|
4
4
|
"description": "MCP server for Claude Code, Cursor, and other MCP hosts \u2014 act as your Ziggs delegate agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
50
|
-
"@ziggs-ai/api-client": "0.37.
|
|
50
|
+
"@ziggs-ai/api-client": "0.37.4",
|
|
51
51
|
"dotenv": "^16.6.1",
|
|
52
52
|
"zod": "^3.24.2",
|
|
53
53
|
"zod-to-json-schema": "^3.25.1"
|