@pellux/goodvibes-sdk 0.25.2 → 0.25.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/_internal/platform/adapters/ntfy/index.d.ts.map +1 -1
- package/dist/_internal/platform/adapters/ntfy/index.js +105 -0
- package/dist/_internal/platform/adapters/types.d.ts +21 -0
- package/dist/_internal/platform/adapters/types.d.ts.map +1 -1
- package/dist/_internal/platform/channels/builtin/account-actions.js +18 -3
- package/dist/_internal/platform/channels/builtin/accounts.d.ts.map +1 -1
- package/dist/_internal/platform/channels/builtin/accounts.js +3 -0
- package/dist/_internal/platform/channels/builtin/plugins.d.ts.map +1 -1
- package/dist/_internal/platform/channels/builtin/plugins.js +3 -0
- package/dist/_internal/platform/channels/builtin/setup-schema.d.ts.map +1 -1
- package/dist/_internal/platform/channels/builtin/setup-schema.js +7 -3
- package/dist/_internal/platform/channels/delivery/strategies-core.d.ts.map +1 -1
- package/dist/_internal/platform/channels/delivery/strategies-core.js +1 -0
- package/dist/_internal/platform/channels/provider-runtime.d.ts +1 -1
- package/dist/_internal/platform/channels/provider-runtime.d.ts.map +1 -1
- package/dist/_internal/platform/channels/provider-runtime.js +14 -9
- package/dist/_internal/platform/channels/reply-pipeline.d.ts +1 -0
- package/dist/_internal/platform/channels/reply-pipeline.d.ts.map +1 -1
- package/dist/_internal/platform/channels/reply-pipeline.js +32 -1
- package/dist/_internal/platform/channels/surface-registry.d.ts.map +1 -1
- package/dist/_internal/platform/channels/surface-registry.js +3 -0
- package/dist/_internal/platform/companion/companion-chat-manager.d.ts +12 -0
- package/dist/_internal/platform/companion/companion-chat-manager.d.ts.map +1 -1
- package/dist/_internal/platform/companion/companion-chat-manager.js +41 -0
- package/dist/_internal/platform/config/schema-domain-surfaces.d.ts +3 -0
- package/dist/_internal/platform/config/schema-domain-surfaces.d.ts.map +1 -1
- package/dist/_internal/platform/config/schema-domain-surfaces.js +22 -1
- package/dist/_internal/platform/config/schema-types.d.ts +5 -2
- package/dist/_internal/platform/config/schema-types.d.ts.map +1 -1
- package/dist/_internal/platform/control-plane/conversation-message.d.ts +1 -1
- package/dist/_internal/platform/control-plane/conversation-message.d.ts.map +1 -1
- package/dist/_internal/platform/daemon/facade-composition.d.ts.map +1 -1
- package/dist/_internal/platform/daemon/facade-composition.js +3 -0
- package/dist/_internal/platform/daemon/surface-actions.d.ts +21 -1
- package/dist/_internal/platform/daemon/surface-actions.d.ts.map +1 -1
- package/dist/_internal/platform/daemon/surface-actions.js +184 -0
- package/dist/_internal/platform/daemon/surface-delivery.d.ts.map +1 -1
- package/dist/_internal/platform/daemon/surface-delivery.js +2 -0
- package/dist/_internal/platform/integrations/index.d.ts +2 -2
- package/dist/_internal/platform/integrations/index.d.ts.map +1 -1
- package/dist/_internal/platform/integrations/index.js +1 -1
- package/dist/_internal/platform/integrations/ntfy.d.ts +23 -0
- package/dist/_internal/platform/integrations/ntfy.d.ts.map +1 -1
- package/dist/_internal/platform/integrations/ntfy.js +281 -30
- package/dist/_internal/platform/providers/registry.d.ts +1 -0
- package/dist/_internal/platform/providers/registry.d.ts.map +1 -1
- package/dist/_internal/platform/providers/registry.js +15 -5
- package/dist/_internal/platform/runtime/emitters/agents.d.ts +3 -0
- package/dist/_internal/platform/runtime/emitters/agents.d.ts.map +1 -1
- package/dist/_internal/platform/runtime/events/agents.d.ts +3 -0
- package/dist/_internal/platform/runtime/events/agents.d.ts.map +1 -1
- package/dist/_internal/platform/tools/agent/manager.d.ts.map +1 -1
- package/dist/_internal/platform/tools/agent/manager.js +3 -0
- package/dist/_internal/platform/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { emitCompanionMessageReceived } from '../runtime/emitters/index.js';
|
|
2
|
+
import { NtfyIntegration } from '../integrations/ntfy.js';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
import { summarizeError } from '../utils/error-display.js';
|
|
1
5
|
export class DaemonSurfaceActionHelper {
|
|
2
6
|
context;
|
|
7
|
+
static NTFY_CHAT_REPLY_TTL_MS = 10 * 60_000;
|
|
8
|
+
pendingNtfyChatReplies = new Map();
|
|
9
|
+
ntfyChatReplyUnsubscribers = [];
|
|
10
|
+
ntfyRemoteSessionId = null;
|
|
3
11
|
constructor(context) {
|
|
4
12
|
this.context = context;
|
|
5
13
|
}
|
|
@@ -15,6 +23,9 @@ export class DaemonSurfaceActionHelper {
|
|
|
15
23
|
performInteractiveSurfaceAction: (actionId, surface, request) => this.performInteractiveSurfaceAction(actionId, surface, request),
|
|
16
24
|
trySpawnAgent: (input, logLabel, sessionId) => this.context.trySpawnAgent(input, logLabel, sessionId),
|
|
17
25
|
queueSurfaceReplyFromBinding: (binding, input) => this.context.queueSurfaceReplyFromBinding(binding, input),
|
|
26
|
+
publishConversationFollowup: (sessionId, envelope) => this.publishConversationFollowup(sessionId, envelope),
|
|
27
|
+
queueNtfyChatReply: (input) => this.queueNtfyChatReply(input),
|
|
28
|
+
postNtfyRemoteChatMessage: (input) => this.postNtfyRemoteChatMessage(input),
|
|
18
29
|
};
|
|
19
30
|
}
|
|
20
31
|
buildGenericWebhookAdapterContext() {
|
|
@@ -121,4 +132,177 @@ export class DaemonSurfaceActionHelper {
|
|
|
121
132
|
}
|
|
122
133
|
return `No handler for ${surface} action ${actionId}`;
|
|
123
134
|
}
|
|
135
|
+
publishConversationFollowup(sessionId, envelope) {
|
|
136
|
+
this.context.controlPlaneGateway.publishEvent('conversation.followup.companion', { sessionId, ...envelope }, { clientKind: 'tui' });
|
|
137
|
+
emitCompanionMessageReceived(this.context.runtimeBus, { sessionId, traceId: `ntfy:${envelope.messageId}`, source: 'ntfy-chat' }, {
|
|
138
|
+
sessionId,
|
|
139
|
+
messageId: envelope.messageId,
|
|
140
|
+
body: envelope.body,
|
|
141
|
+
source: envelope.source,
|
|
142
|
+
timestamp: envelope.timestamp,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
queueNtfyChatReply(input) {
|
|
146
|
+
this.ensureNtfyChatReplyListeners();
|
|
147
|
+
this.cleanupExpiredNtfyChatReplies();
|
|
148
|
+
const bucket = this.pendingNtfyChatReplies.get(input.sessionId) ?? [];
|
|
149
|
+
bucket.push({
|
|
150
|
+
...input,
|
|
151
|
+
createdAt: Date.now(),
|
|
152
|
+
});
|
|
153
|
+
this.pendingNtfyChatReplies.set(input.sessionId, bucket);
|
|
154
|
+
}
|
|
155
|
+
ensureNtfyChatReplyListeners() {
|
|
156
|
+
if (this.ntfyChatReplyUnsubscribers.length > 0)
|
|
157
|
+
return;
|
|
158
|
+
this.ntfyChatReplyUnsubscribers = [
|
|
159
|
+
this.context.runtimeBus.on('TURN_SUBMITTED', (envelope) => this.matchNtfyChatReplyTurn(envelope.sessionId, envelope.payload.turnId, envelope.payload.prompt)),
|
|
160
|
+
this.context.runtimeBus.on('TURN_COMPLETED', (envelope) => {
|
|
161
|
+
void this.deliverNtfyChatReply(envelope.sessionId, envelope.payload.turnId, envelope.payload.response);
|
|
162
|
+
}),
|
|
163
|
+
this.context.runtimeBus.on('TURN_ERROR', (envelope) => {
|
|
164
|
+
void this.deliverNtfyChatReply(envelope.sessionId, envelope.payload.turnId, `Error: ${envelope.payload.error}`);
|
|
165
|
+
}),
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
matchNtfyChatReplyTurn(sessionId, turnId, prompt) {
|
|
169
|
+
this.cleanupExpiredNtfyChatReplies();
|
|
170
|
+
const bucket = this.pendingNtfyChatReplies.get(sessionId);
|
|
171
|
+
if (!bucket)
|
|
172
|
+
return;
|
|
173
|
+
const normalizedPrompt = prompt.trim();
|
|
174
|
+
const pending = bucket.find((entry) => !entry.turnId && entry.body.trim() === normalizedPrompt);
|
|
175
|
+
if (pending) {
|
|
176
|
+
pending.turnId = turnId;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async deliverNtfyChatReply(sessionId, turnId, message) {
|
|
180
|
+
const pending = this.takeNtfyChatReply(sessionId, turnId);
|
|
181
|
+
if (!pending)
|
|
182
|
+
return;
|
|
183
|
+
try {
|
|
184
|
+
await this.publishNtfyReply(pending.topic, message.trim() || '(empty response)', pending.title ?? 'GoodVibes chat');
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
logger.warn('DaemonSurfaceActionHelper: failed to publish ntfy chat reply', {
|
|
188
|
+
sessionId,
|
|
189
|
+
turnId,
|
|
190
|
+
topic: pending.topic,
|
|
191
|
+
error: summarizeError(error),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
takeNtfyChatReply(sessionId, turnId) {
|
|
196
|
+
const bucket = this.pendingNtfyChatReplies.get(sessionId);
|
|
197
|
+
if (!bucket)
|
|
198
|
+
return null;
|
|
199
|
+
const index = bucket.findIndex((entry) => entry.turnId === turnId);
|
|
200
|
+
if (index < 0)
|
|
201
|
+
return null;
|
|
202
|
+
const [pending] = bucket.splice(index, 1);
|
|
203
|
+
if (bucket.length === 0) {
|
|
204
|
+
this.pendingNtfyChatReplies.delete(sessionId);
|
|
205
|
+
}
|
|
206
|
+
return pending ?? null;
|
|
207
|
+
}
|
|
208
|
+
cleanupExpiredNtfyChatReplies(now = Date.now()) {
|
|
209
|
+
for (const [sessionId, bucket] of this.pendingNtfyChatReplies.entries()) {
|
|
210
|
+
const fresh = bucket.filter((entry) => now - entry.createdAt < DaemonSurfaceActionHelper.NTFY_CHAT_REPLY_TTL_MS);
|
|
211
|
+
if (fresh.length === 0) {
|
|
212
|
+
this.pendingNtfyChatReplies.delete(sessionId);
|
|
213
|
+
}
|
|
214
|
+
else if (fresh.length !== bucket.length) {
|
|
215
|
+
this.pendingNtfyChatReplies.set(sessionId, fresh);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async postNtfyRemoteChatMessage(input) {
|
|
220
|
+
const manager = this.context.companionChatManager;
|
|
221
|
+
if (!manager) {
|
|
222
|
+
return {
|
|
223
|
+
sessionId: '',
|
|
224
|
+
messageId: '',
|
|
225
|
+
delivered: false,
|
|
226
|
+
error: 'ntfy remote chat manager is unavailable',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
let sessionId = this.ntfyRemoteSessionId ?? '';
|
|
230
|
+
try {
|
|
231
|
+
await manager.init();
|
|
232
|
+
let session = sessionId ? manager.getSession(sessionId) : null;
|
|
233
|
+
if (!session || session.status === 'closed') {
|
|
234
|
+
session = manager.createSession({
|
|
235
|
+
title: input.title ?? 'GoodVibes ntfy',
|
|
236
|
+
});
|
|
237
|
+
this.ntfyRemoteSessionId = session.id;
|
|
238
|
+
}
|
|
239
|
+
sessionId = session.id;
|
|
240
|
+
void this.runNtfyRemoteChatTurn(manager, sessionId, input).catch((error) => {
|
|
241
|
+
logger.warn('DaemonSurfaceActionHelper: ntfy remote chat turn failed', {
|
|
242
|
+
sessionId,
|
|
243
|
+
topic: input.topic,
|
|
244
|
+
error: summarizeError(error),
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
sessionId,
|
|
249
|
+
messageId: '',
|
|
250
|
+
delivered: true,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
const errorMessage = summarizeError(error);
|
|
255
|
+
try {
|
|
256
|
+
await this.publishNtfyReply(input.topic, `Error: ${errorMessage}`, input.title ?? 'GoodVibes ntfy');
|
|
257
|
+
}
|
|
258
|
+
catch (publishError) {
|
|
259
|
+
logger.warn('DaemonSurfaceActionHelper: failed to publish ntfy remote chat error', {
|
|
260
|
+
topic: input.topic,
|
|
261
|
+
error: summarizeError(publishError),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
sessionId,
|
|
266
|
+
messageId: '',
|
|
267
|
+
delivered: false,
|
|
268
|
+
error: errorMessage,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async runNtfyRemoteChatTurn(manager, sessionId, input) {
|
|
273
|
+
try {
|
|
274
|
+
const result = await manager.postMessageAndWaitForReply(sessionId, input.body, `ntfy:${input.topic}`, { timeoutMs: 120_000 });
|
|
275
|
+
const response = result.response?.trim();
|
|
276
|
+
const resultError = result.error ?? (response ? undefined : 'No response from ntfy remote chat');
|
|
277
|
+
const outbound = response || `Error: ${resultError}`;
|
|
278
|
+
await this.publishNtfyReply(input.topic, outbound, input.title ?? 'GoodVibes ntfy');
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
const errorMessage = summarizeError(error);
|
|
282
|
+
try {
|
|
283
|
+
await this.publishNtfyReply(input.topic, `Error: ${errorMessage}`, input.title ?? 'GoodVibes ntfy');
|
|
284
|
+
}
|
|
285
|
+
catch (publishError) {
|
|
286
|
+
logger.warn('DaemonSurfaceActionHelper: failed to publish ntfy remote chat error', {
|
|
287
|
+
topic: input.topic,
|
|
288
|
+
error: summarizeError(publishError),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async publishNtfyReply(topic, message, title) {
|
|
294
|
+
if (!topic || !message.trim())
|
|
295
|
+
return;
|
|
296
|
+
const ntfy = new NtfyIntegration(String(this.context.configManager.get('surfaces.ntfy.baseUrl') || 'https://ntfy.sh'), await this.resolveNtfyToken() ?? undefined);
|
|
297
|
+
await ntfy.publish(topic, message, {
|
|
298
|
+
title,
|
|
299
|
+
markGoodVibesOrigin: true,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
async resolveNtfyToken() {
|
|
303
|
+
return await this.context.serviceRegistry.resolveSecret('ntfy', 'primary')
|
|
304
|
+
|| String(this.context.configManager.get('surfaces.ntfy.token') || '')
|
|
305
|
+
|| process.env.NTFY_ACCESS_TOKEN
|
|
306
|
+
|| null;
|
|
307
|
+
}
|
|
124
308
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface-delivery.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/daemon/surface-delivery.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"surface-delivery.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/daemon/surface-delivery.ts"],"names":[],"mappings":"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"}
|
|
@@ -201,6 +201,7 @@ export class DaemonSurfaceDeliveryHelper {
|
|
|
201
201
|
],
|
|
202
202
|
}
|
|
203
203
|
: {}),
|
|
204
|
+
markGoodVibesOrigin: true,
|
|
204
205
|
});
|
|
205
206
|
}
|
|
206
207
|
async deliverWebhookAgentReply(pending, message) {
|
|
@@ -428,6 +429,7 @@ export class DaemonSurfaceDeliveryHelper {
|
|
|
428
429
|
await ntfy.publish(topic, `${isPending ? 'Approval required' : `Approval ${approval.status}`}: ${summary}`, {
|
|
429
430
|
title: approval.request.tool,
|
|
430
431
|
...(webUrl ? { click: webUrl } : {}),
|
|
432
|
+
markGoodVibesOrigin: true,
|
|
431
433
|
}).catch(() => { });
|
|
432
434
|
}
|
|
433
435
|
async deliverWebhookApprovalUpdate(approval, binding) {
|
|
@@ -7,7 +7,7 @@ export { Notifier } from './notifier.js';
|
|
|
7
7
|
export { GitHubIntegration } from './github.js';
|
|
8
8
|
export type { GitHubWebhookEvent } from './github.js';
|
|
9
9
|
export { DeliveryQueue, DeliveryError, classifyDeliveryError, snapshotQueueStatus } from './delivery.js';
|
|
10
|
-
export { NtfyIntegration } from './ntfy.js';
|
|
10
|
+
export { GOODVIBES_NTFY_ORIGIN, GOODVIBES_NTFY_ORIGIN_HEADER, GOODVIBES_NTFY_OUTBOUND_TAG, GOODVIBES_NTFY_AGENT_TOPIC, GOODVIBES_NTFY_CHAT_TOPIC, GOODVIBES_NTFY_DEFAULT_TOPICS, GOODVIBES_NTFY_REMOTE_TOPIC, NtfyIntegration, isGoodVibesNtfyDeliveryEcho, resolveGoodVibesNtfyTopics, } from './ntfy.js';
|
|
11
11
|
export type { DeliveryOutcome, DeliveryFailureClass, DeadLetterEntry, DeliveryMetrics, DeliveryQueueConfig, IntegrationQueueStatus, } from './delivery.js';
|
|
12
|
-
export type { NtfyMessage, NtfyPublishOptions, NtfySubscribeOptions, NtfyWebSocketOptions } from './ntfy.js';
|
|
12
|
+
export type { GoodVibesNtfyTopicConfig, GoodVibesNtfyTopics, NtfyMessage, NtfyPublishOptions, NtfySubscribeOptions, NtfyWebSocketOptions, } from './ntfy.js';
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/integrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACrE,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,4BAA4B,EAC5B,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACpH,YAAY,EACV,yBAAyB,EACzB,+BAA+B,EAC/B,yBAAyB,EACzB,2BAA2B,EAC3B,sBAAsB,EACtB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/integrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACrE,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,4BAA4B,EAC5B,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACpH,YAAY,EACV,yBAAyB,EACzB,+BAA+B,EAC/B,yBAAyB,EACzB,2BAA2B,EAC3B,sBAAsB,EACtB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,0BAA0B,EAC1B,yBAAyB,EACzB,6BAA6B,EAC7B,2BAA2B,EAC3B,eAAe,EACf,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,wBAAwB,EACxB,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,WAAW,CAAC"}
|
|
@@ -4,4 +4,4 @@ export { DiscordInteractionType, DiscordInteractionResponseType } from './discor
|
|
|
4
4
|
export { Notifier } from './notifier.js';
|
|
5
5
|
export { GitHubIntegration } from './github.js';
|
|
6
6
|
export { DeliveryQueue, DeliveryError, classifyDeliveryError, snapshotQueueStatus } from './delivery.js';
|
|
7
|
-
export { NtfyIntegration } from './ntfy.js';
|
|
7
|
+
export { GOODVIBES_NTFY_ORIGIN, GOODVIBES_NTFY_ORIGIN_HEADER, GOODVIBES_NTFY_OUTBOUND_TAG, GOODVIBES_NTFY_AGENT_TOPIC, GOODVIBES_NTFY_CHAT_TOPIC, GOODVIBES_NTFY_DEFAULT_TOPICS, GOODVIBES_NTFY_REMOTE_TOPIC, NtfyIntegration, isGoodVibesNtfyDeliveryEcho, resolveGoodVibesNtfyTopics, } from './ntfy.js';
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
export declare const GOODVIBES_NTFY_ORIGIN = "goodvibes-sdk";
|
|
2
|
+
export declare const GOODVIBES_NTFY_ORIGIN_HEADER = "X-Goodvibes-Origin";
|
|
3
|
+
export declare const GOODVIBES_NTFY_OUTBOUND_TAG = "goodvibes-sdk-outbound";
|
|
4
|
+
export declare const GOODVIBES_NTFY_CHAT_TOPIC = "goodvibes-chat";
|
|
5
|
+
export declare const GOODVIBES_NTFY_AGENT_TOPIC = "goodvibes-agent";
|
|
6
|
+
export declare const GOODVIBES_NTFY_REMOTE_TOPIC = "goodvibes-ntfy";
|
|
7
|
+
export declare const GOODVIBES_NTFY_DEFAULT_TOPICS: readonly ["goodvibes-chat", "goodvibes-agent", "goodvibes-ntfy"];
|
|
8
|
+
export interface GoodVibesNtfyTopicConfig {
|
|
9
|
+
readonly chatTopic?: string | null;
|
|
10
|
+
readonly agentTopic?: string | null;
|
|
11
|
+
readonly remoteTopic?: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface GoodVibesNtfyTopics {
|
|
14
|
+
readonly chatTopic: string;
|
|
15
|
+
readonly agentTopic: string;
|
|
16
|
+
readonly remoteTopic: string;
|
|
17
|
+
readonly all: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
export declare function resolveGoodVibesNtfyTopics(config?: GoodVibesNtfyTopicConfig): GoodVibesNtfyTopics;
|
|
1
20
|
export interface NtfyPublishOptions {
|
|
2
21
|
readonly title?: string;
|
|
3
22
|
readonly priority?: 1 | 2 | 3 | 4 | 5;
|
|
@@ -5,6 +24,7 @@ export interface NtfyPublishOptions {
|
|
|
5
24
|
readonly click?: string;
|
|
6
25
|
readonly attach?: string;
|
|
7
26
|
readonly actions?: readonly string[];
|
|
27
|
+
readonly markGoodVibesOrigin?: boolean;
|
|
8
28
|
}
|
|
9
29
|
export interface NtfyMessage {
|
|
10
30
|
readonly id?: string;
|
|
@@ -25,6 +45,8 @@ export interface NtfySubscribeOptions {
|
|
|
25
45
|
readonly poll?: boolean;
|
|
26
46
|
readonly filters?: Record<string, string | number | boolean | readonly string[]>;
|
|
27
47
|
readonly signal?: AbortSignal;
|
|
48
|
+
readonly reconnect?: boolean;
|
|
49
|
+
readonly reconnectDelayMs?: number;
|
|
28
50
|
}
|
|
29
51
|
export interface NtfyWebSocketOptions extends NtfySubscribeOptions {
|
|
30
52
|
readonly WebSocketImpl?: typeof WebSocket;
|
|
@@ -40,4 +62,5 @@ export declare class NtfyIntegration {
|
|
|
40
62
|
connectWebSocket(topic: string, onMessage: (message: NtfyMessage) => void | Promise<void>, options?: NtfyWebSocketOptions): WebSocket;
|
|
41
63
|
private buildAuthHeaders;
|
|
42
64
|
}
|
|
65
|
+
export declare function isGoodVibesNtfyDeliveryEcho(message: Record<string, unknown>): boolean;
|
|
43
66
|
//# sourceMappingURL=ntfy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ntfy.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/integrations/ntfy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ntfy.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/integrations/ntfy.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
/** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
|
|
2
|
+
import { request as httpRequest } from 'node:http';
|
|
3
|
+
import { connect as http2Connect } from 'node:http2';
|
|
4
|
+
import { request as httpsRequest } from 'node:https';
|
|
2
5
|
import { fetchWithTimeout } from '../utils/fetch-with-timeout.js';
|
|
3
6
|
import { instrumentedFetch } from '../utils/fetch-with-timeout.js';
|
|
7
|
+
export const GOODVIBES_NTFY_ORIGIN = 'goodvibes-sdk';
|
|
8
|
+
export const GOODVIBES_NTFY_ORIGIN_HEADER = 'X-Goodvibes-Origin';
|
|
9
|
+
export const GOODVIBES_NTFY_OUTBOUND_TAG = 'goodvibes-sdk-outbound';
|
|
10
|
+
export const GOODVIBES_NTFY_CHAT_TOPIC = 'goodvibes-chat';
|
|
11
|
+
export const GOODVIBES_NTFY_AGENT_TOPIC = 'goodvibes-agent';
|
|
12
|
+
export const GOODVIBES_NTFY_REMOTE_TOPIC = 'goodvibes-ntfy';
|
|
13
|
+
export const GOODVIBES_NTFY_DEFAULT_TOPICS = [
|
|
14
|
+
GOODVIBES_NTFY_CHAT_TOPIC,
|
|
15
|
+
GOODVIBES_NTFY_AGENT_TOPIC,
|
|
16
|
+
GOODVIBES_NTFY_REMOTE_TOPIC,
|
|
17
|
+
];
|
|
18
|
+
export function resolveGoodVibesNtfyTopics(config = {}) {
|
|
19
|
+
const chatTopic = normalizeNtfyTopic(config.chatTopic, GOODVIBES_NTFY_CHAT_TOPIC);
|
|
20
|
+
const agentTopic = normalizeNtfyTopic(config.agentTopic, GOODVIBES_NTFY_AGENT_TOPIC);
|
|
21
|
+
const remoteTopic = normalizeNtfyTopic(config.remoteTopic, GOODVIBES_NTFY_REMOTE_TOPIC);
|
|
22
|
+
return {
|
|
23
|
+
chatTopic,
|
|
24
|
+
agentTopic,
|
|
25
|
+
remoteTopic,
|
|
26
|
+
all: [...new Set([chatTopic, agentTopic, remoteTopic])],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
4
29
|
export class NtfyIntegration {
|
|
5
30
|
baseUrl;
|
|
6
31
|
token;
|
|
@@ -17,14 +42,19 @@ export class NtfyIntegration {
|
|
|
17
42
|
headers.set('Title', options.title);
|
|
18
43
|
if (options.priority)
|
|
19
44
|
headers.set('Priority', String(options.priority));
|
|
20
|
-
|
|
21
|
-
|
|
45
|
+
const tags = options.markGoodVibesOrigin
|
|
46
|
+
? [...new Set([...(options.tags ?? []), GOODVIBES_NTFY_OUTBOUND_TAG])]
|
|
47
|
+
: options.tags ?? [];
|
|
48
|
+
if (tags.length)
|
|
49
|
+
headers.set('Tags', tags.join(','));
|
|
22
50
|
if (options.click)
|
|
23
51
|
headers.set('Click', options.click);
|
|
24
52
|
if (options.attach)
|
|
25
53
|
headers.set('Attach', options.attach);
|
|
26
54
|
if (options.actions?.length)
|
|
27
55
|
headers.set('Actions', options.actions.join(';'));
|
|
56
|
+
if (options.markGoodVibesOrigin)
|
|
57
|
+
headers.set(GOODVIBES_NTFY_ORIGIN_HEADER, GOODVIBES_NTFY_ORIGIN);
|
|
28
58
|
if (this.token)
|
|
29
59
|
headers.set('Authorization', `Bearer ${this.token}`);
|
|
30
60
|
const response = await fetchWithTimeout(target, {
|
|
@@ -74,35 +104,21 @@ export class NtfyIntegration {
|
|
|
74
104
|
.map((line) => JSON.parse(line));
|
|
75
105
|
}
|
|
76
106
|
async subscribeJsonStream(topic, onMessage, options = {}) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
83
|
-
if (!response.ok || !response.body) {
|
|
84
|
-
const body = await response.text().catch(() => '');
|
|
85
|
-
throw new Error(`NtfyIntegration.subscribeJsonStream failed (${response.status}): ${body}`);
|
|
86
|
-
}
|
|
87
|
-
const reader = response.body.getReader();
|
|
88
|
-
const decoder = new TextDecoder();
|
|
89
|
-
let buffer = '';
|
|
90
|
-
while (true) {
|
|
91
|
-
const { done, value } = await reader.read();
|
|
92
|
-
if (done)
|
|
93
|
-
break;
|
|
94
|
-
buffer += decoder.decode(value, { stream: true });
|
|
95
|
-
const lines = buffer.split(/\r?\n/);
|
|
96
|
-
buffer = lines.pop() ?? '';
|
|
97
|
-
for (const line of lines) {
|
|
98
|
-
const trimmed = line.trim();
|
|
99
|
-
if (!trimmed)
|
|
100
|
-
continue;
|
|
101
|
-
await onMessage(JSON.parse(trimmed));
|
|
107
|
+
const reconnect = options.reconnect ?? !options.poll;
|
|
108
|
+
while (!options.signal?.aborted) {
|
|
109
|
+
const url = this.buildSubscribeUrl(topic, 'json', options);
|
|
110
|
+
try {
|
|
111
|
+
await readNtfyJsonStreamWithNodeTransport(url, this.buildAuthHeaders(), onMessage, options.signal);
|
|
102
112
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (options.signal?.aborted)
|
|
115
|
+
return;
|
|
116
|
+
if (!reconnect || isFatalNtfyStreamError(error))
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
if (!reconnect || options.signal?.aborted)
|
|
120
|
+
return;
|
|
121
|
+
await waitForNtfyReconnectDelay(options.signal, options.reconnectDelayMs ?? 1_000);
|
|
106
122
|
}
|
|
107
123
|
}
|
|
108
124
|
connectWebSocket(topic, onMessage, options = {}) {
|
|
@@ -129,3 +145,238 @@ export class NtfyIntegration {
|
|
|
129
145
|
return headers;
|
|
130
146
|
}
|
|
131
147
|
}
|
|
148
|
+
export function isGoodVibesNtfyDeliveryEcho(message) {
|
|
149
|
+
if (message.event !== 'message')
|
|
150
|
+
return false;
|
|
151
|
+
const tags = message.tags;
|
|
152
|
+
if (Array.isArray(tags) && tags.includes(GOODVIBES_NTFY_OUTBOUND_TAG))
|
|
153
|
+
return true;
|
|
154
|
+
const headers = readRecord(message.headers);
|
|
155
|
+
const originHeader = headers ? readCaseInsensitiveHeader(headers, GOODVIBES_NTFY_ORIGIN_HEADER) : undefined;
|
|
156
|
+
return originHeader === GOODVIBES_NTFY_ORIGIN;
|
|
157
|
+
}
|
|
158
|
+
function normalizeNtfyTopic(value, fallback) {
|
|
159
|
+
const topic = typeof value === 'string' ? value.trim() : '';
|
|
160
|
+
return topic || fallback;
|
|
161
|
+
}
|
|
162
|
+
class NtfyStreamHttpError extends Error {
|
|
163
|
+
status;
|
|
164
|
+
constructor(status, body) {
|
|
165
|
+
super(`NtfyIntegration.subscribeJsonStream failed (${status}): ${body}`);
|
|
166
|
+
this.status = status;
|
|
167
|
+
this.name = 'NtfyStreamHttpError';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function readNtfyJsonStreamWithNodeTransport(url, headers, onMessage, signal) {
|
|
171
|
+
const parsed = new URL(url);
|
|
172
|
+
if (parsed.protocol === 'http:') {
|
|
173
|
+
return readNtfyJsonStreamWithHttp1(url, headers, onMessage, signal, httpRequest);
|
|
174
|
+
}
|
|
175
|
+
if (parsed.protocol === 'https:') {
|
|
176
|
+
try {
|
|
177
|
+
await readNtfyJsonStreamWithHttp2(url, headers, onMessage, signal);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (signal?.aborted || error instanceof NtfyStreamHttpError)
|
|
182
|
+
throw error;
|
|
183
|
+
return readNtfyJsonStreamWithHttp1(url, headers, onMessage, signal, httpsRequest);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
throw new Error(`Unsupported ntfy stream protocol: ${parsed.protocol}`);
|
|
187
|
+
}
|
|
188
|
+
function readNtfyJsonStreamWithHttp1(url, headers, onMessage, signal, requestImpl) {
|
|
189
|
+
if (signal?.aborted)
|
|
190
|
+
return Promise.resolve();
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
let settled = false;
|
|
193
|
+
let aborted = false;
|
|
194
|
+
const state = { buffer: '', queue: Promise.resolve() };
|
|
195
|
+
const settle = (callback) => {
|
|
196
|
+
if (settled)
|
|
197
|
+
return;
|
|
198
|
+
settled = true;
|
|
199
|
+
signal?.removeEventListener('abort', abort);
|
|
200
|
+
callback();
|
|
201
|
+
};
|
|
202
|
+
const fail = (error) => settle(() => reject(error));
|
|
203
|
+
const finish = () => settle(resolve);
|
|
204
|
+
const abort = () => {
|
|
205
|
+
aborted = true;
|
|
206
|
+
req.destroy();
|
|
207
|
+
finish();
|
|
208
|
+
};
|
|
209
|
+
const req = requestImpl(url, {
|
|
210
|
+
method: 'GET',
|
|
211
|
+
headers: headersToRecord(headers),
|
|
212
|
+
}, (res) => {
|
|
213
|
+
res.setEncoding('utf8');
|
|
214
|
+
const status = res.statusCode ?? 0;
|
|
215
|
+
if (status < 200 || status >= 300) {
|
|
216
|
+
let body = '';
|
|
217
|
+
res.on('data', (chunk) => {
|
|
218
|
+
if (body.length < 4096)
|
|
219
|
+
body += chunk;
|
|
220
|
+
});
|
|
221
|
+
res.on('end', () => fail(new NtfyStreamHttpError(status, body)));
|
|
222
|
+
res.on('error', fail);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
res.on('data', (chunk) => {
|
|
226
|
+
state.queue = state.queue
|
|
227
|
+
.then(() => consumeNtfyJsonChunk(state, chunk, onMessage))
|
|
228
|
+
.catch(fail);
|
|
229
|
+
});
|
|
230
|
+
res.on('end', () => {
|
|
231
|
+
state.queue
|
|
232
|
+
.then(() => flushNtfyJsonBuffer(state, onMessage))
|
|
233
|
+
.then(finish, fail);
|
|
234
|
+
});
|
|
235
|
+
res.on('error', (error) => {
|
|
236
|
+
if (aborted)
|
|
237
|
+
finish();
|
|
238
|
+
else
|
|
239
|
+
fail(error);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
243
|
+
req.on('error', (error) => {
|
|
244
|
+
if (aborted)
|
|
245
|
+
finish();
|
|
246
|
+
else
|
|
247
|
+
fail(error);
|
|
248
|
+
});
|
|
249
|
+
req.end();
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
function readNtfyJsonStreamWithHttp2(url, headers, onMessage, signal) {
|
|
253
|
+
if (signal?.aborted)
|
|
254
|
+
return Promise.resolve();
|
|
255
|
+
return new Promise((resolve, reject) => {
|
|
256
|
+
const parsed = new URL(url);
|
|
257
|
+
const client = http2Connect(parsed.origin);
|
|
258
|
+
const path = `${parsed.pathname}${parsed.search}`;
|
|
259
|
+
const state = { buffer: '', queue: Promise.resolve() };
|
|
260
|
+
let settled = false;
|
|
261
|
+
let aborted = false;
|
|
262
|
+
let status = 0;
|
|
263
|
+
let errorBody = '';
|
|
264
|
+
const settle = (callback) => {
|
|
265
|
+
if (settled)
|
|
266
|
+
return;
|
|
267
|
+
settled = true;
|
|
268
|
+
signal?.removeEventListener('abort', abort);
|
|
269
|
+
client.close();
|
|
270
|
+
callback();
|
|
271
|
+
};
|
|
272
|
+
const fail = (error) => settle(() => reject(error));
|
|
273
|
+
const finish = () => settle(resolve);
|
|
274
|
+
const abort = () => {
|
|
275
|
+
aborted = true;
|
|
276
|
+
stream.close();
|
|
277
|
+
finish();
|
|
278
|
+
};
|
|
279
|
+
const stream = client.request({
|
|
280
|
+
':method': 'GET',
|
|
281
|
+
':path': path,
|
|
282
|
+
...headersToRecord(headers),
|
|
283
|
+
});
|
|
284
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
285
|
+
stream.setEncoding('utf8');
|
|
286
|
+
stream.on('response', (responseHeaders) => {
|
|
287
|
+
status = Number(responseHeaders[':status'] ?? 0);
|
|
288
|
+
});
|
|
289
|
+
stream.on('data', (chunk) => {
|
|
290
|
+
if (status !== 0 && (status < 200 || status >= 300)) {
|
|
291
|
+
if (errorBody.length < 4096)
|
|
292
|
+
errorBody += chunk;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
state.queue = state.queue
|
|
296
|
+
.then(() => consumeNtfyJsonChunk(state, chunk, onMessage))
|
|
297
|
+
.catch(fail);
|
|
298
|
+
});
|
|
299
|
+
stream.on('end', () => {
|
|
300
|
+
if (status < 200 || status >= 300) {
|
|
301
|
+
fail(new NtfyStreamHttpError(status, errorBody));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
state.queue
|
|
305
|
+
.then(() => flushNtfyJsonBuffer(state, onMessage))
|
|
306
|
+
.then(finish, fail);
|
|
307
|
+
});
|
|
308
|
+
stream.on('error', (error) => {
|
|
309
|
+
if (aborted)
|
|
310
|
+
finish();
|
|
311
|
+
else
|
|
312
|
+
fail(error);
|
|
313
|
+
});
|
|
314
|
+
client.on('error', (error) => {
|
|
315
|
+
if (aborted)
|
|
316
|
+
finish();
|
|
317
|
+
else
|
|
318
|
+
fail(error);
|
|
319
|
+
});
|
|
320
|
+
stream.end();
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function headersToRecord(headers) {
|
|
324
|
+
const record = {};
|
|
325
|
+
headers.forEach((value, key) => {
|
|
326
|
+
record[key.toLowerCase()] = value;
|
|
327
|
+
});
|
|
328
|
+
return record;
|
|
329
|
+
}
|
|
330
|
+
async function consumeNtfyJsonChunk(state, chunk, onMessage) {
|
|
331
|
+
state.buffer += chunk;
|
|
332
|
+
const lines = state.buffer.split(/\r?\n/);
|
|
333
|
+
state.buffer = lines.pop() ?? '';
|
|
334
|
+
for (const line of lines) {
|
|
335
|
+
const trimmed = line.trim();
|
|
336
|
+
if (!trimmed)
|
|
337
|
+
continue;
|
|
338
|
+
await onMessage(JSON.parse(trimmed));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async function flushNtfyJsonBuffer(state, onMessage) {
|
|
342
|
+
const trimmed = state.buffer.trim();
|
|
343
|
+
state.buffer = '';
|
|
344
|
+
if (trimmed)
|
|
345
|
+
await onMessage(JSON.parse(trimmed));
|
|
346
|
+
}
|
|
347
|
+
function isFatalNtfyStreamError(error) {
|
|
348
|
+
return error instanceof NtfyStreamHttpError
|
|
349
|
+
&& error.status >= 400
|
|
350
|
+
&& error.status < 500
|
|
351
|
+
&& error.status !== 408
|
|
352
|
+
&& error.status !== 429;
|
|
353
|
+
}
|
|
354
|
+
function waitForNtfyReconnectDelay(signal, delayMs) {
|
|
355
|
+
if (signal?.aborted || delayMs <= 0)
|
|
356
|
+
return Promise.resolve();
|
|
357
|
+
return new Promise((resolve) => {
|
|
358
|
+
let timeout;
|
|
359
|
+
const done = () => {
|
|
360
|
+
clearTimeout(timeout);
|
|
361
|
+
signal?.removeEventListener('abort', abort);
|
|
362
|
+
resolve();
|
|
363
|
+
};
|
|
364
|
+
const abort = () => done();
|
|
365
|
+
timeout = setTimeout(done, delayMs);
|
|
366
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
function readRecord(value) {
|
|
370
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
371
|
+
return null;
|
|
372
|
+
return value;
|
|
373
|
+
}
|
|
374
|
+
function readCaseInsensitiveHeader(headers, name) {
|
|
375
|
+
const wanted = name.toLowerCase();
|
|
376
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
377
|
+
if (key.toLowerCase() !== wanted)
|
|
378
|
+
continue;
|
|
379
|
+
return typeof value === 'string' ? value : undefined;
|
|
380
|
+
}
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
@@ -118,6 +118,7 @@ export declare class ProviderRegistry {
|
|
|
118
118
|
* Explicit provider constraints do not fall through to other providers.
|
|
119
119
|
*/
|
|
120
120
|
getForModel(modelId: string, provider?: string): LLMProvider;
|
|
121
|
+
private getExplicitOpenAIProviderForStaleCatalog;
|
|
121
122
|
/** All registered model definitions. */
|
|
122
123
|
listModels(): ModelDefinition[];
|
|
123
124
|
getCostFromCatalog(modelId: string): {
|