@gr33n-ai/jade-sdk-client 0.1.0 → 0.1.3

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.
@@ -1,2070 +0,0 @@
1
- import { createContext, useMemo, useContext, useState, useRef, useCallback } from 'react';
2
- import { jsx } from 'react/jsx-runtime';
3
- import useSWR from 'swr';
4
-
5
- // ../../agent-sdk/client/dist/react/index.mjs
6
-
7
- // ../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1/node_modules/@microsoft/fetch-event-source/lib/esm/parse.js
8
- async function getBytes(stream, onChunk) {
9
- const reader = stream.getReader();
10
- let result;
11
- while (!(result = await reader.read()).done) {
12
- onChunk(result.value);
13
- }
14
- }
15
- function getLines(onLine) {
16
- let buffer;
17
- let position;
18
- let fieldLength;
19
- let discardTrailingNewline = false;
20
- return function onChunk(arr) {
21
- if (buffer === void 0) {
22
- buffer = arr;
23
- position = 0;
24
- fieldLength = -1;
25
- } else {
26
- buffer = concat(buffer, arr);
27
- }
28
- const bufLength = buffer.length;
29
- let lineStart = 0;
30
- while (position < bufLength) {
31
- if (discardTrailingNewline) {
32
- if (buffer[position] === 10) {
33
- lineStart = ++position;
34
- }
35
- discardTrailingNewline = false;
36
- }
37
- let lineEnd = -1;
38
- for (; position < bufLength && lineEnd === -1; ++position) {
39
- switch (buffer[position]) {
40
- case 58:
41
- if (fieldLength === -1) {
42
- fieldLength = position - lineStart;
43
- }
44
- break;
45
- case 13:
46
- discardTrailingNewline = true;
47
- case 10:
48
- lineEnd = position;
49
- break;
50
- }
51
- }
52
- if (lineEnd === -1) {
53
- break;
54
- }
55
- onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
56
- lineStart = position;
57
- fieldLength = -1;
58
- }
59
- if (lineStart === bufLength) {
60
- buffer = void 0;
61
- } else if (lineStart !== 0) {
62
- buffer = buffer.subarray(lineStart);
63
- position -= lineStart;
64
- }
65
- };
66
- }
67
- function getMessages(onId, onRetry, onMessage) {
68
- let message = newMessage();
69
- const decoder = new TextDecoder();
70
- return function onLine(line, fieldLength) {
71
- if (line.length === 0) {
72
- onMessage === null || onMessage === void 0 ? void 0 : onMessage(message);
73
- message = newMessage();
74
- } else if (fieldLength > 0) {
75
- const field = decoder.decode(line.subarray(0, fieldLength));
76
- const valueOffset = fieldLength + (line[fieldLength + 1] === 32 ? 2 : 1);
77
- const value = decoder.decode(line.subarray(valueOffset));
78
- switch (field) {
79
- case "data":
80
- message.data = message.data ? message.data + "\n" + value : value;
81
- break;
82
- case "event":
83
- message.event = value;
84
- break;
85
- case "id":
86
- onId(message.id = value);
87
- break;
88
- case "retry":
89
- const retry = parseInt(value, 10);
90
- if (!isNaN(retry)) {
91
- onRetry(message.retry = retry);
92
- }
93
- break;
94
- }
95
- }
96
- };
97
- }
98
- function concat(a, b) {
99
- const res = new Uint8Array(a.length + b.length);
100
- res.set(a);
101
- res.set(b, a.length);
102
- return res;
103
- }
104
- function newMessage() {
105
- return {
106
- data: "",
107
- event: "",
108
- id: "",
109
- retry: void 0
110
- };
111
- }
112
-
113
- // ../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1/node_modules/@microsoft/fetch-event-source/lib/esm/fetch.js
114
- var __rest = function(s, e) {
115
- var t = {};
116
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
117
- t[p] = s[p];
118
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
119
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
120
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
121
- t[p[i]] = s[p[i]];
122
- }
123
- return t;
124
- };
125
- var EventStreamContentType = "text/event-stream";
126
- var DefaultRetryInterval = 1e3;
127
- var LastEventId = "last-event-id";
128
- function fetchEventSource(input, _a) {
129
- var { signal: inputSignal, headers: inputHeaders, onopen: inputOnOpen, onmessage, onclose, onerror, openWhenHidden, fetch: inputFetch } = _a, rest = __rest(_a, ["signal", "headers", "onopen", "onmessage", "onclose", "onerror", "openWhenHidden", "fetch"]);
130
- return new Promise((resolve, reject) => {
131
- const headers = Object.assign({}, inputHeaders);
132
- if (!headers.accept) {
133
- headers.accept = EventStreamContentType;
134
- }
135
- let curRequestController;
136
- function onVisibilityChange() {
137
- curRequestController.abort();
138
- if (!document.hidden) {
139
- create();
140
- }
141
- }
142
- if (!openWhenHidden) {
143
- document.addEventListener("visibilitychange", onVisibilityChange);
144
- }
145
- let retryInterval = DefaultRetryInterval;
146
- let retryTimer = 0;
147
- function dispose() {
148
- document.removeEventListener("visibilitychange", onVisibilityChange);
149
- window.clearTimeout(retryTimer);
150
- curRequestController.abort();
151
- }
152
- inputSignal === null || inputSignal === void 0 ? void 0 : inputSignal.addEventListener("abort", () => {
153
- dispose();
154
- resolve();
155
- });
156
- const fetch2 = inputFetch !== null && inputFetch !== void 0 ? inputFetch : window.fetch;
157
- const onopen = inputOnOpen !== null && inputOnOpen !== void 0 ? inputOnOpen : defaultOnOpen;
158
- async function create() {
159
- var _a2;
160
- curRequestController = new AbortController();
161
- try {
162
- const response = await fetch2(input, Object.assign(Object.assign({}, rest), { headers, signal: curRequestController.signal }));
163
- await onopen(response);
164
- await getBytes(response.body, getLines(getMessages((id) => {
165
- if (id) {
166
- headers[LastEventId] = id;
167
- } else {
168
- delete headers[LastEventId];
169
- }
170
- }, (retry) => {
171
- retryInterval = retry;
172
- }, onmessage)));
173
- onclose === null || onclose === void 0 ? void 0 : onclose();
174
- dispose();
175
- resolve();
176
- } catch (err) {
177
- if (!curRequestController.signal.aborted) {
178
- try {
179
- const interval = (_a2 = onerror === null || onerror === void 0 ? void 0 : onerror(err)) !== null && _a2 !== void 0 ? _a2 : retryInterval;
180
- window.clearTimeout(retryTimer);
181
- retryTimer = window.setTimeout(create, interval);
182
- } catch (innerErr) {
183
- dispose();
184
- reject(innerErr);
185
- }
186
- }
187
- }
188
- }
189
- create();
190
- });
191
- }
192
- function defaultOnOpen(response) {
193
- const contentType = response.headers.get("content-type");
194
- if (!(contentType === null || contentType === void 0 ? void 0 : contentType.startsWith(EventStreamContentType))) {
195
- throw new Error(`Expected content-type to be ${EventStreamContentType}, Actual: ${contentType}`);
196
- }
197
- }
198
- var AgentEventEmitter = class {
199
- constructor() {
200
- this.handlers = /* @__PURE__ */ new Map();
201
- }
202
- /**
203
- * Subscribe to an event.
204
- * @returns Unsubscribe function
205
- */
206
- on(event, handler) {
207
- if (!this.handlers.has(event)) {
208
- this.handlers.set(event, /* @__PURE__ */ new Set());
209
- }
210
- this.handlers.get(event).add(handler);
211
- return () => this.off(event, handler);
212
- }
213
- /**
214
- * Unsubscribe from an event.
215
- */
216
- off(event, handler) {
217
- this.handlers.get(event)?.delete(handler);
218
- }
219
- /**
220
- * Emit an event to all handlers.
221
- */
222
- emit(event, data) {
223
- this.handlers.get(event)?.forEach((handler) => {
224
- try {
225
- handler(data);
226
- } catch (err) {
227
- console.error(`[AgentEventEmitter] Error in ${event} handler:`, err);
228
- }
229
- });
230
- }
231
- /**
232
- * Remove all event handlers.
233
- */
234
- removeAllListeners() {
235
- this.handlers.clear();
236
- }
237
- };
238
- var AgentClientError = class extends Error {
239
- constructor(message, cause) {
240
- super(message);
241
- this.cause = cause;
242
- this.name = "AgentClientError";
243
- }
244
- };
245
- var AuthenticationError = class extends AgentClientError {
246
- constructor(message = "Authentication failed") {
247
- super(message);
248
- this.name = "AuthenticationError";
249
- }
250
- };
251
- var SessionNotFoundError = class extends AgentClientError {
252
- constructor(sessionId) {
253
- super(`Session not found: ${sessionId}`);
254
- this.sessionId = sessionId;
255
- this.name = "SessionNotFoundError";
256
- }
257
- };
258
- var SkillNotFoundError = class extends AgentClientError {
259
- constructor(skillName) {
260
- super(`Skill not found: ${skillName}`);
261
- this.skillName = skillName;
262
- this.name = "SkillNotFoundError";
263
- }
264
- };
265
- var RequestError = class extends AgentClientError {
266
- constructor(message, statusCode) {
267
- super(message);
268
- this.statusCode = statusCode;
269
- this.name = "RequestError";
270
- }
271
- };
272
- var FatalSSEError = class extends Error {
273
- constructor(message) {
274
- super(message);
275
- this.name = "FatalSSEError";
276
- }
277
- };
278
- var MAX_RETRIES = 5;
279
- var INITIAL_RETRY_DELAY = 1e3;
280
- var MAX_RETRY_DELAY = 1e4;
281
- async function createSSEStream(options) {
282
- const {
283
- url,
284
- method,
285
- headers,
286
- body,
287
- emitter,
288
- signal,
289
- onOpen,
290
- onClose,
291
- lastEventId
292
- } = options;
293
- const processedIds = /* @__PURE__ */ new Set();
294
- let retryCount = 0;
295
- try {
296
- await fetchEventSource(url, {
297
- method,
298
- headers: {
299
- "Content-Type": "application/json",
300
- ...lastEventId ? { "Last-Event-ID": lastEventId } : {},
301
- ...headers
302
- },
303
- body: JSON.stringify(body),
304
- signal,
305
- async onopen(response) {
306
- if (response.ok && response.headers.get("content-type")?.includes(EventStreamContentType)) {
307
- onOpen?.();
308
- return;
309
- }
310
- if (response.status === 401 || response.status === 403) {
311
- throw new FatalSSEError(`Authentication failed: ${response.status}`);
312
- }
313
- if (response.status === 404) {
314
- throw new FatalSSEError("Session not found or not active");
315
- }
316
- if (response.status === 503) {
317
- const text = await response.text().catch(() => "Service unavailable");
318
- throw new FatalSSEError(`Server unavailable: ${text}`);
319
- }
320
- if (response.status >= 400 && response.status < 500) {
321
- const text = await response.text().catch(() => `Error ${response.status}`);
322
- throw new FatalSSEError(text);
323
- }
324
- throw new Error(`Failed to connect: ${response.status}`);
325
- },
326
- onmessage(event) {
327
- if (event.id && processedIds.has(event.id)) {
328
- return;
329
- }
330
- if (event.id) {
331
- processedIds.add(event.id);
332
- }
333
- if (!event.data) {
334
- return;
335
- }
336
- try {
337
- const data = JSON.parse(event.data);
338
- const eventType = event.event || "message";
339
- switch (eventType) {
340
- case "init":
341
- case "session":
342
- emitter.emit("session", { session_id: data.session_id });
343
- break;
344
- case "entry":
345
- emitter.emit("entry", { entry: data });
346
- break;
347
- case "stream_event":
348
- handleStreamEvent(data, emitter);
349
- break;
350
- case "content_block_start":
351
- emitter.emit("content_block_start", data);
352
- break;
353
- case "content_block_delta":
354
- emitter.emit("content_block_delta", data);
355
- break;
356
- case "content_block_stop":
357
- emitter.emit("content_block_stop", data);
358
- break;
359
- case "complete":
360
- emitter.emit("complete", data);
361
- break;
362
- case "error":
363
- emitter.emit("error", { error: data.error });
364
- break;
365
- case "heartbeat":
366
- emitter.emit("heartbeat", {});
367
- break;
368
- default:
369
- break;
370
- }
371
- } catch (e) {
372
- console.error("[SSE] Failed to parse event:", event.data, e);
373
- }
374
- },
375
- onerror(err) {
376
- if (signal?.aborted) {
377
- throw err;
378
- }
379
- if (err instanceof FatalSSEError) {
380
- emitter.emit("error", { error: err.message });
381
- throw err;
382
- }
383
- retryCount++;
384
- if (retryCount > MAX_RETRIES) {
385
- const error = `Connection failed after ${MAX_RETRIES} retries`;
386
- emitter.emit("error", { error });
387
- throw new FatalSSEError(error);
388
- }
389
- const delay = Math.min(INITIAL_RETRY_DELAY * Math.pow(2, retryCount - 1), MAX_RETRY_DELAY);
390
- console.log(`[SSE] Retry ${retryCount}/${MAX_RETRIES} in ${delay}ms`);
391
- return delay;
392
- },
393
- onclose() {
394
- onClose?.();
395
- },
396
- // Keep connection open when tab is hidden - heartbeats keep it alive
397
- openWhenHidden: true
398
- });
399
- } catch (error) {
400
- if (error instanceof FatalSSEError) {
401
- if (error.message.includes("Authentication")) {
402
- throw new AuthenticationError(error.message);
403
- }
404
- throw new RequestError(error.message, 500);
405
- }
406
- if (error instanceof Error) {
407
- emitter.emit("error", { error: error.message });
408
- }
409
- throw error;
410
- }
411
- }
412
- function handleStreamEvent(data, emitter) {
413
- const streamEvent = data.event || data;
414
- const eventData = streamEvent;
415
- switch (eventData.type) {
416
- case "content_block_start":
417
- emitter.emit("content_block_start", eventData);
418
- break;
419
- case "content_block_delta":
420
- emitter.emit("content_block_delta", eventData);
421
- break;
422
- case "content_block_stop":
423
- emitter.emit("content_block_stop", eventData);
424
- break;
425
- }
426
- }
427
- var AgentClient = class {
428
- constructor(config) {
429
- this.activeStreams = /* @__PURE__ */ new Map();
430
- this.config = {
431
- apiVersion: "/v1",
432
- timeout: 3e4,
433
- ...config
434
- };
435
- }
436
- // ===========================================================================
437
- // Public getters
438
- // ===========================================================================
439
- /** Whether org context is configured (orgId is set) */
440
- get hasOrgContext() {
441
- return !!this.config.orgId;
442
- }
443
- // ===========================================================================
444
- // Private helpers
445
- // ===========================================================================
446
- get baseUrl() {
447
- return `${this.config.endpoint}${this.config.apiVersion}`;
448
- }
449
- async getHeaders() {
450
- const headers = {
451
- "Content-Type": "application/json"
452
- };
453
- const token = await this.config.getAuthToken();
454
- if (token) {
455
- headers["Authorization"] = `Bearer ${token}`;
456
- }
457
- return headers;
458
- }
459
- async request(path, options = {}) {
460
- const url = `${this.baseUrl}${path}`;
461
- const headers = await this.getHeaders();
462
- const controller = new AbortController();
463
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
464
- try {
465
- const response = await fetch(url, {
466
- ...options,
467
- headers: { ...headers, ...options.headers || {} },
468
- signal: controller.signal
469
- });
470
- if (!response.ok) {
471
- await this.handleErrorResponse(response, path);
472
- }
473
- return response.json();
474
- } finally {
475
- clearTimeout(timeoutId);
476
- }
477
- }
478
- async handleErrorResponse(response, path) {
479
- if (response.status === 401 || response.status === 403) {
480
- throw new AuthenticationError();
481
- }
482
- if (response.status === 404) {
483
- const text = await response.text();
484
- if (text.toLowerCase().includes("session")) {
485
- const sessionId = path.split("/").pop() || "";
486
- throw new SessionNotFoundError(sessionId);
487
- }
488
- if (text.toLowerCase().includes("skill")) {
489
- const skillName = path.split("/").pop() || "";
490
- throw new SkillNotFoundError(skillName);
491
- }
492
- }
493
- throw new RequestError(`Request failed: ${response.status}`, response.status);
494
- }
495
- // ===========================================================================
496
- // Messages API
497
- // ===========================================================================
498
- /**
499
- * Send a message and stream responses.
500
- * @returns Object with event emitter and abort function
501
- */
502
- stream(request) {
503
- const emitter = new AgentEventEmitter();
504
- const controller = new AbortController();
505
- const streamId = request.session_id || `stream-${Date.now()}`;
506
- this.activeStreams.set(streamId, controller);
507
- (async () => {
508
- try {
509
- const headers = await this.getHeaders();
510
- await createSSEStream({
511
- url: `${this.baseUrl}/messages`,
512
- method: "POST",
513
- headers,
514
- body: { ...request, stream: true },
515
- emitter,
516
- signal: controller.signal
517
- });
518
- } catch (error) {
519
- emitter.emit("error", {
520
- error: error instanceof Error ? error.message : "Unknown error"
521
- });
522
- } finally {
523
- this.activeStreams.delete(streamId);
524
- }
525
- })();
526
- return {
527
- emitter,
528
- abort: () => controller.abort()
529
- };
530
- }
531
- // ===========================================================================
532
- // Sessions API
533
- // ===========================================================================
534
- /**
535
- * List all sessions for the authenticated user.
536
- */
537
- async listSessions() {
538
- return this.request("/sessions");
539
- }
540
- /**
541
- * Get session content in display format.
542
- */
543
- async getSession(sessionId) {
544
- return this.request(`/sessions/${sessionId}`);
545
- }
546
- /**
547
- * Get session content as raw JSONL.
548
- */
549
- async getSessionRaw(sessionId) {
550
- return this.request(`/sessions/${sessionId}?format=raw`);
551
- }
552
- /**
553
- * Check if a session is actively streaming.
554
- */
555
- async getSessionStatus(sessionId) {
556
- return this.request(`/sessions/${sessionId}/status`);
557
- }
558
- /**
559
- * Update session metadata (name).
560
- */
561
- async updateSession(sessionId, updates) {
562
- return this.request(`/sessions/${sessionId}`, {
563
- method: "PATCH",
564
- body: JSON.stringify(updates)
565
- });
566
- }
567
- /**
568
- * Delete a session.
569
- */
570
- async deleteSession(sessionId) {
571
- return this.request(`/sessions/${sessionId}`, {
572
- method: "DELETE"
573
- });
574
- }
575
- /**
576
- * Cancel an active session query.
577
- */
578
- async cancelSession(sessionId) {
579
- return this.request(`/sessions/${sessionId}/cancel`, {
580
- method: "POST"
581
- });
582
- }
583
- /**
584
- * Reconnect to an active streaming session.
585
- * @returns Object with event emitter and abort function
586
- */
587
- reconnect(sessionId, lastEventId = -1) {
588
- const emitter = new AgentEventEmitter();
589
- const controller = new AbortController();
590
- (async () => {
591
- try {
592
- const headers = await this.getHeaders();
593
- await createSSEStream({
594
- url: `${this.baseUrl}/sessions/${sessionId}/reconnect`,
595
- method: "POST",
596
- headers,
597
- body: { last_event_id: lastEventId },
598
- emitter,
599
- signal: controller.signal
600
- });
601
- } catch (error) {
602
- emitter.emit("error", {
603
- error: error instanceof Error ? error.message : "Unknown error"
604
- });
605
- }
606
- })();
607
- return {
608
- emitter,
609
- abort: () => controller.abort()
610
- };
611
- }
612
- // ===========================================================================
613
- // Skills API - Personal
614
- // ===========================================================================
615
- /**
616
- * List personal skills.
617
- */
618
- async listSkills() {
619
- return this.request("/skills");
620
- }
621
- /**
622
- * Get personal skill content.
623
- */
624
- async getSkill(name) {
625
- const response = await this.request(`/skills/${name}`);
626
- return this.decodeBase64(response.content);
627
- }
628
- /**
629
- * Create or update a personal skill.
630
- */
631
- async saveSkill(skill) {
632
- const content = this.encodeBase64(skill.content);
633
- return this.request("/skills", {
634
- method: "POST",
635
- body: JSON.stringify({ ...skill, content })
636
- });
637
- }
638
- /**
639
- * Delete a personal skill.
640
- */
641
- async deleteSkill(name) {
642
- return this.request(`/skills/${name}`, {
643
- method: "DELETE"
644
- });
645
- }
646
- // ===========================================================================
647
- // Skills API - Organization
648
- // ===========================================================================
649
- /**
650
- * List organization skills.
651
- */
652
- async listOrgSkills() {
653
- return this.request("/skills/org");
654
- }
655
- /**
656
- * Get organization skill content.
657
- */
658
- async getOrgSkill(name) {
659
- const response = await this.request(
660
- `/skills/org/${name}`
661
- );
662
- return this.decodeBase64(response.content);
663
- }
664
- /**
665
- * Create or update an organization skill.
666
- */
667
- async saveOrgSkill(skill) {
668
- const content = this.encodeBase64(skill.content);
669
- return this.request("/skills/org", {
670
- method: "POST",
671
- body: JSON.stringify({ ...skill, content })
672
- });
673
- }
674
- /**
675
- * Delete an organization skill.
676
- */
677
- async deleteOrgSkill(name) {
678
- return this.request(`/skills/org/${name}`, {
679
- method: "DELETE"
680
- });
681
- }
682
- // ===========================================================================
683
- // Plugins API
684
- // ===========================================================================
685
- /**
686
- * Get plugin manifest for client sync.
687
- */
688
- async getPluginManifest() {
689
- return this.request("/plugins/manifest");
690
- }
691
- /**
692
- * Download plugin bundle.
693
- */
694
- async getPluginBundle(skills) {
695
- const query = skills?.length ? `?skills=${skills.join(",")}` : "";
696
- return this.request(`/plugins/bundle${query}`);
697
- }
698
- // ===========================================================================
699
- // Utility methods
700
- // ===========================================================================
701
- encodeBase64(data) {
702
- if (typeof data === "string") {
703
- const encoder = new TextEncoder();
704
- data = encoder.encode(data);
705
- }
706
- return btoa(String.fromCharCode(...data));
707
- }
708
- decodeBase64(base64) {
709
- return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
710
- }
711
- };
712
- var AgentContext = createContext(null);
713
- function AgentProvider({ children, config }) {
714
- const client = useMemo(
715
- () => new AgentClient(config),
716
- // Only recreate client if endpoint changes
717
- // getAuthToken is a function and should not trigger recreation
718
- [config.endpoint, config.apiVersion, config.timeout]
719
- );
720
- return /* @__PURE__ */ jsx(AgentContext.Provider, { value: { client }, children });
721
- }
722
- function useAgentClient() {
723
- const context = useContext(AgentContext);
724
- if (!context) {
725
- throw new Error("useAgentClient must be used within an AgentProvider");
726
- }
727
- return context.client;
728
- }
729
- function useAgentSession(options = {}) {
730
- const client = useAgentClient();
731
- const { initialSessionId, initialConversation, onMediaGenerated } = options;
732
- const [session, setSession] = useState({
733
- sessionId: initialSessionId,
734
- conversation: initialConversation ?? [],
735
- isStreaming: false
736
- });
737
- const abortRef = useRef(null);
738
- const accumulatedTextRef = useRef({});
739
- const setupEventHandlers = useCallback(
740
- (emitter) => {
741
- emitter.on("session", (data) => {
742
- setSession((prev) => ({ ...prev, sessionId: data.session_id }));
743
- });
744
- emitter.on("content_block_start", (data) => {
745
- const block = data.content_block;
746
- if (block?.type === "text") {
747
- accumulatedTextRef.current[data.index] = "";
748
- setSession((prev) => ({ ...prev, showTinkering: false }));
749
- } else if (block?.type === "tool_use") {
750
- setSession((prev) => ({
751
- ...prev,
752
- streamingToolCall: {
753
- tool_name: block.name || "",
754
- tool_use_id: block.id || "",
755
- block_index: data.index,
756
- accumulated_input: ""
757
- },
758
- showTinkering: false
759
- }));
760
- }
761
- });
762
- emitter.on("content_block_delta", (data) => {
763
- const delta = data.delta;
764
- if (delta?.type === "text_delta" && delta.text) {
765
- const idx = data.index ?? 0;
766
- accumulatedTextRef.current[idx] = (accumulatedTextRef.current[idx] || "") + delta.text;
767
- setSession((prev) => ({
768
- ...prev,
769
- streamingText: accumulatedTextRef.current[idx],
770
- streamingBlockIndex: idx,
771
- showTinkering: false
772
- }));
773
- } else if (delta?.type === "input_json_delta" && delta.partial_json) {
774
- setSession((prev) => {
775
- if (!prev.streamingToolCall) return prev;
776
- return {
777
- ...prev,
778
- streamingToolCall: {
779
- ...prev.streamingToolCall,
780
- accumulated_input: (prev.streamingToolCall.accumulated_input || "") + delta.partial_json
781
- }
782
- };
783
- });
784
- }
785
- });
786
- emitter.on("content_block_stop", (data) => {
787
- const idx = data.index ?? 0;
788
- if (accumulatedTextRef.current[idx] !== void 0) {
789
- delete accumulatedTextRef.current[idx];
790
- setSession((prev) => ({
791
- ...prev,
792
- streamingText: void 0,
793
- streamingBlockIndex: void 0
794
- }));
795
- }
796
- });
797
- emitter.on("entry", (data) => {
798
- const entry = data.entry;
799
- if (entry.type === "tool_result" && onMediaGenerated) {
800
- const media = extractMediaFromEntry(entry);
801
- if (media) {
802
- onMediaGenerated(media.urls, media.type);
803
- }
804
- }
805
- if (entry.type === "tool_call") {
806
- setSession((prev) => ({
807
- ...prev,
808
- conversation: [...prev.conversation, entry],
809
- streamingToolCall: void 0
810
- }));
811
- } else {
812
- setSession((prev) => ({
813
- ...prev,
814
- conversation: [...prev.conversation, entry]
815
- }));
816
- }
817
- });
818
- emitter.on("complete", (data) => {
819
- setSession((prev) => ({
820
- ...prev,
821
- sessionId: data.session_id || prev.sessionId,
822
- isStreaming: false,
823
- showTinkering: false
824
- }));
825
- abortRef.current = null;
826
- });
827
- emitter.on("error", (data) => {
828
- setSession((prev) => ({
829
- ...prev,
830
- conversation: [
831
- ...prev.conversation,
832
- {
833
- type: "assistant_text",
834
- text: `Error: ${data.error}`,
835
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
836
- }
837
- ],
838
- isStreaming: false,
839
- showTinkering: false
840
- }));
841
- abortRef.current = null;
842
- });
843
- },
844
- [onMediaGenerated]
845
- );
846
- const sendMessage = useCallback(
847
- async (prompt, skills) => {
848
- if (!prompt.trim() || session.isStreaming) return;
849
- const userEntry = {
850
- type: "user_text",
851
- text: prompt,
852
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
853
- };
854
- setSession((prev) => ({
855
- ...prev,
856
- conversation: [...prev.conversation, userEntry],
857
- isStreaming: true,
858
- showTinkering: true
859
- }));
860
- accumulatedTextRef.current = {};
861
- const { emitter, abort } = client.stream({
862
- prompt,
863
- session_id: session.sessionId,
864
- skills
865
- });
866
- abortRef.current = abort;
867
- setupEventHandlers(emitter);
868
- },
869
- [client, session.sessionId, session.isStreaming, setupEventHandlers]
870
- );
871
- const cancel = useCallback(async () => {
872
- abortRef.current?.();
873
- abortRef.current = null;
874
- if (session.sessionId) {
875
- try {
876
- await client.cancelSession(session.sessionId);
877
- } catch (error) {
878
- console.error("[Cancel] Server-side cancel failed:", error);
879
- }
880
- }
881
- setSession((prev) => ({
882
- ...prev,
883
- isStreaming: false,
884
- showTinkering: false,
885
- streamingText: void 0,
886
- streamingToolCall: void 0
887
- }));
888
- }, [client, session.sessionId]);
889
- const clear = useCallback(() => {
890
- setSession({
891
- conversation: [],
892
- isStreaming: false
893
- });
894
- }, []);
895
- const setSessionId = useCallback((id) => {
896
- setSession((prev) => ({ ...prev, sessionId: id }));
897
- }, []);
898
- const setConversation = useCallback(
899
- (entries, sessionId) => {
900
- setSession((prev) => ({
901
- ...prev,
902
- conversation: entries,
903
- sessionId: sessionId ?? prev.sessionId,
904
- isStreaming: false,
905
- streamingText: void 0,
906
- streamingToolCall: void 0,
907
- showTinkering: false
908
- }));
909
- },
910
- []
911
- );
912
- const reconnect = useCallback(
913
- async (targetSessionId, freshConversation, streamingPrompt) => {
914
- let conversation = freshConversation ?? [];
915
- if (streamingPrompt) {
916
- conversation = [
917
- ...conversation,
918
- {
919
- type: "user_text",
920
- text: streamingPrompt,
921
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
922
- }
923
- ];
924
- }
925
- setSession((prev) => ({
926
- ...prev,
927
- sessionId: targetSessionId,
928
- conversation,
929
- isStreaming: true,
930
- showTinkering: true
931
- }));
932
- accumulatedTextRef.current = {};
933
- const { emitter, abort } = client.reconnect(targetSessionId, -1);
934
- abortRef.current = abort;
935
- setupEventHandlers(emitter);
936
- },
937
- [client, setupEventHandlers]
938
- );
939
- const loadSession = useCallback(
940
- async (sessionId) => {
941
- const response = await client.getSession(sessionId);
942
- return response.conversation;
943
- },
944
- [client]
945
- );
946
- return {
947
- sessionId: session.sessionId,
948
- conversation: session.conversation,
949
- isStreaming: session.isStreaming,
950
- streamingText: session.streamingText,
951
- streamingToolCall: session.streamingToolCall,
952
- showTinkering: session.showTinkering ?? false,
953
- sendMessage,
954
- cancel,
955
- clear,
956
- reconnect,
957
- setSessionId,
958
- setConversation,
959
- loadSession
960
- };
961
- }
962
- function detectMediaType(url) {
963
- if (/\.(mp4|mov|webm|avi|mkv|m4v)(\?|$)/i.test(url)) return "video";
964
- if (/\.(mp3|wav|m4a|aac|ogg)(\?|$)/i.test(url)) return "audio";
965
- return "image";
966
- }
967
- function extractMediaFromEntry(entry) {
968
- if (entry.type !== "tool_result") return null;
969
- let result = null;
970
- if (typeof entry.content === "string") {
971
- try {
972
- result = JSON.parse(entry.content);
973
- } catch {
974
- return null;
975
- }
976
- }
977
- if (!result) return null;
978
- const mediaInfo = result.mediaInfo || result.data?.mediaInfo;
979
- if (mediaInfo?.urls && mediaInfo.urls.length > 0) {
980
- const type = detectMediaType(mediaInfo.urls[0]);
981
- return { urls: mediaInfo.urls, type };
982
- }
983
- const cdnUrls = result.output_cdn_urls || result.data?.output_cdn_urls;
984
- if (cdnUrls && cdnUrls.length > 0) {
985
- const type = detectMediaType(cdnUrls[0]);
986
- return { urls: cdnUrls, type };
987
- }
988
- return null;
989
- }
990
- function useSkills() {
991
- const client = useAgentClient();
992
- const [isSaving, setIsSaving] = useState(false);
993
- const {
994
- data: skillsData,
995
- error: skillsError,
996
- mutate: mutateSkills,
997
- isLoading: isLoadingSkills
998
- } = useSWR("agent-sdk-skills", () => client.listSkills(), {
999
- revalidateOnFocus: false,
1000
- dedupingInterval: 6e4
1001
- // 1 minute
1002
- });
1003
- const {
1004
- data: orgSkillsData,
1005
- error: orgSkillsError,
1006
- mutate: mutateOrgSkills,
1007
- isLoading: isLoadingOrgSkills
1008
- } = useSWR(
1009
- client.hasOrgContext ? "agent-sdk-skills-org" : null,
1010
- () => client.listOrgSkills(),
1011
- {
1012
- revalidateOnFocus: false,
1013
- dedupingInterval: 6e4
1014
- }
1015
- );
1016
- const saveSkill = useCallback(
1017
- async (skill) => {
1018
- setIsSaving(true);
1019
- try {
1020
- await client.saveSkill(skill);
1021
- await mutateSkills();
1022
- } finally {
1023
- setIsSaving(false);
1024
- }
1025
- },
1026
- [client, mutateSkills]
1027
- );
1028
- const deleteSkill = useCallback(
1029
- async (name) => {
1030
- await client.deleteSkill(name);
1031
- await mutateSkills();
1032
- },
1033
- [client, mutateSkills]
1034
- );
1035
- const refresh = useCallback(async () => {
1036
- await mutateSkills();
1037
- }, [mutateSkills]);
1038
- const saveOrgSkill = useCallback(
1039
- async (skill) => {
1040
- setIsSaving(true);
1041
- try {
1042
- await client.saveOrgSkill(skill);
1043
- await mutateOrgSkills();
1044
- } finally {
1045
- setIsSaving(false);
1046
- }
1047
- },
1048
- [client, mutateOrgSkills]
1049
- );
1050
- const deleteOrgSkill = useCallback(
1051
- async (name) => {
1052
- await client.deleteOrgSkill(name);
1053
- await mutateOrgSkills();
1054
- },
1055
- [client, mutateOrgSkills]
1056
- );
1057
- const refreshOrg = useCallback(async () => {
1058
- await mutateOrgSkills();
1059
- }, [mutateOrgSkills]);
1060
- return {
1061
- // Personal skills
1062
- skills: skillsData?.skills ?? [],
1063
- isLoading: isLoadingSkills || isSaving,
1064
- error: skillsError,
1065
- refresh,
1066
- saveSkill,
1067
- deleteSkill,
1068
- // Org skills
1069
- orgSkills: orgSkillsData?.skills ?? [],
1070
- isLoadingOrg: isLoadingOrgSkills,
1071
- orgError: orgSkillsError,
1072
- refreshOrg,
1073
- saveOrgSkill,
1074
- deleteOrgSkill
1075
- };
1076
- }
1077
-
1078
- // src/jade/tools/parsers.ts
1079
- function parseToolResultContent(content) {
1080
- try {
1081
- const pythonMatch = content.match(/\[\{'type':\s*'text',\s*'text':\s*'([\s\S]+)'\}\]$/);
1082
- if (pythonMatch) {
1083
- const innerText = pythonMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"');
1084
- const parsed2 = JSON.parse(innerText);
1085
- if (parsed2 && typeof parsed2 === "object" && !Array.isArray(parsed2)) {
1086
- return parsed2;
1087
- }
1088
- }
1089
- const parsed = JSON.parse(content);
1090
- if (Array.isArray(parsed) && parsed.length > 0 && parsed[0].type === "text" && parsed[0].text) {
1091
- const unescapedText = parsed[0].text.replace(/\\n/g, "\n").replace(/\\"/g, '"');
1092
- const innerParsed = JSON.parse(unescapedText);
1093
- if (innerParsed && typeof innerParsed === "object" && !Array.isArray(innerParsed)) {
1094
- return innerParsed;
1095
- }
1096
- }
1097
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1098
- return parsed;
1099
- }
1100
- } catch {
1101
- }
1102
- const jadeUrlMatch = content.match(/https?:\/\/jade\.gr33n\.ai\/media\/([a-zA-Z0-9_-]+)/);
1103
- const falUrl = extractFalMediaUrl(content);
1104
- if (jadeUrlMatch || falUrl) {
1105
- const request_id = jadeUrlMatch ? jadeUrlMatch[1] : "unknown";
1106
- const output_cdn_urls = falUrl ? [falUrl] : [];
1107
- return {
1108
- request_id,
1109
- output_cdn_urls,
1110
- status: "completed"
1111
- };
1112
- }
1113
- return null;
1114
- }
1115
- function extractFalMediaUrl(content) {
1116
- const urlMatch = content.match(/https?:\/\/[^\/]*fal\.media\/files\/[^\s"')]+/);
1117
- return urlMatch ? urlMatch[0] : null;
1118
- }
1119
- function extractMediaInfoFromToolResult(content) {
1120
- const parsed = parseToolResultContent(content);
1121
- if (!parsed) return null;
1122
- const urls = parsed.output_cdn_urls || parsed.urls;
1123
- if (!parsed.request_id || typeof parsed.request_id !== "string" || !Array.isArray(urls) || urls.length === 0) {
1124
- return null;
1125
- }
1126
- return {
1127
- requestId: parsed.request_id,
1128
- urls,
1129
- fileType: parsed.file_type,
1130
- metadata: parsed.metadata,
1131
- originalSource: parsed.original_source
1132
- };
1133
- }
1134
- function createMediaParseResult(displayType) {
1135
- return (content) => {
1136
- const mediaInfo = extractMediaInfoFromToolResult(content);
1137
- if (mediaInfo) {
1138
- return {
1139
- type: displayType,
1140
- data: { mediaInfo }
1141
- };
1142
- }
1143
- return null;
1144
- };
1145
- }
1146
- function extractMediaInfo(content) {
1147
- try {
1148
- const parsed = parseToolResultContent(content);
1149
- if (parsed?.request_id) {
1150
- const urls2 = parsed.output_cdn_urls || parsed.urls;
1151
- if (Array.isArray(urls2) && urls2.length > 0) {
1152
- return {
1153
- requestId: parsed.request_id,
1154
- urls: urls2
1155
- };
1156
- }
1157
- }
1158
- } catch {
1159
- }
1160
- const jadeUrlMatch = content.match(/https?:\/\/jade\.gr33n\.ai\/media\/([a-zA-Z0-9_-]+)/);
1161
- if (!jadeUrlMatch) return null;
1162
- const requestId = jadeUrlMatch[1];
1163
- const cdnUrlMatches = content.matchAll(/https?:\/\/[^\/]*fal\.media\/files\/[^\s"')]+/g);
1164
- const urls = Array.from(cdnUrlMatches, (match) => match[0]);
1165
- if (urls.length === 0) return null;
1166
- return {
1167
- requestId,
1168
- urls
1169
- };
1170
- }
1171
-
1172
- // src/jade/tools/registry.ts
1173
- var TOOL_REGISTRY = {
1174
- "Skill": {
1175
- name: "Skill",
1176
- displayName: "Skill",
1177
- iconName: "BookOpenText",
1178
- category: "system",
1179
- consumesResult: true,
1180
- parseInput: (input) => {
1181
- const inputObj = input;
1182
- const command = inputObj?.skill || inputObj?.command;
1183
- if (!command) return null;
1184
- return {
1185
- type: "skill",
1186
- data: { command }
1187
- };
1188
- },
1189
- parseResult: (content) => ({
1190
- type: "skill_result",
1191
- data: { content }
1192
- })
1193
- },
1194
- "Bash": {
1195
- name: "Bash",
1196
- displayName: "Bash",
1197
- iconName: "Terminal",
1198
- category: "system",
1199
- consumesResult: true,
1200
- parseInput: (input) => {
1201
- const inputObj = input;
1202
- return {
1203
- type: "bash",
1204
- data: {
1205
- command: inputObj?.command,
1206
- description: inputObj?.description
1207
- }
1208
- };
1209
- },
1210
- parseResult: (content) => ({
1211
- type: "bash_output",
1212
- data: { output: content }
1213
- })
1214
- },
1215
- "TodoWrite": {
1216
- name: "TodoWrite",
1217
- displayName: "Todo List",
1218
- iconName: "ListTodo",
1219
- category: "system",
1220
- consumesResult: true,
1221
- parseInput: (input) => {
1222
- const inputObj = input;
1223
- if (!inputObj?.todos) return null;
1224
- return {
1225
- type: "todo_list",
1226
- data: { todos: inputObj.todos }
1227
- };
1228
- },
1229
- parseResult: (content) => ({
1230
- type: "todo_result",
1231
- data: { content }
1232
- })
1233
- },
1234
- "Read": {
1235
- name: "Read",
1236
- displayName: "Read",
1237
- iconName: "BookOpenCheck",
1238
- category: "system",
1239
- consumesResult: true,
1240
- parseInput: (input) => {
1241
- const inputObj = input;
1242
- if (!inputObj?.file_path) return null;
1243
- return {
1244
- type: "read",
1245
- data: {
1246
- file_path: inputObj.file_path,
1247
- offset: inputObj.offset,
1248
- limit: inputObj.limit
1249
- }
1250
- };
1251
- },
1252
- parseResult: (content) => ({
1253
- type: "read_output",
1254
- data: { output: content }
1255
- })
1256
- },
1257
- "Glob": {
1258
- name: "Glob",
1259
- displayName: "File Search",
1260
- iconName: "FolderSearch",
1261
- category: "system",
1262
- consumesResult: true,
1263
- parseInput: (input) => {
1264
- const inputObj = input;
1265
- if (!inputObj?.pattern) return null;
1266
- return {
1267
- type: "glob",
1268
- data: {
1269
- pattern: inputObj.pattern,
1270
- path: inputObj.path
1271
- }
1272
- };
1273
- },
1274
- parseResult: (content) => {
1275
- const files = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
1276
- return {
1277
- type: "glob_output",
1278
- data: { files }
1279
- };
1280
- }
1281
- },
1282
- "WebSearch": {
1283
- name: "WebSearch",
1284
- displayName: "Web Search",
1285
- iconName: "Search",
1286
- category: "system",
1287
- consumesResult: true,
1288
- parseInput: (input) => {
1289
- const inputObj = input;
1290
- if (!inputObj?.query) return null;
1291
- return {
1292
- type: "web_search",
1293
- data: {
1294
- query: inputObj.query,
1295
- allowed_domains: inputObj.allowed_domains,
1296
- blocked_domains: inputObj.blocked_domains
1297
- }
1298
- };
1299
- },
1300
- parseResult: (content) => ({
1301
- type: "web_search_output",
1302
- data: { output: content }
1303
- })
1304
- },
1305
- "Edit": {
1306
- name: "Edit",
1307
- displayName: "Edit File",
1308
- iconName: "Pencil",
1309
- category: "system",
1310
- consumesResult: true,
1311
- parseInput: (input) => {
1312
- const inputObj = input;
1313
- if (!inputObj?.file_path) return null;
1314
- return {
1315
- type: "edit",
1316
- data: {
1317
- file_path: inputObj.file_path,
1318
- old_string: inputObj.old_string,
1319
- new_string: inputObj.new_string,
1320
- replace_all: inputObj.replace_all
1321
- }
1322
- };
1323
- },
1324
- parseResult: (content) => ({
1325
- type: "edit_result",
1326
- data: { output: content }
1327
- })
1328
- },
1329
- "Write": {
1330
- name: "Write",
1331
- displayName: "Write File",
1332
- iconName: "FilePlus",
1333
- category: "system",
1334
- consumesResult: true,
1335
- parseInput: (input) => {
1336
- const inputObj = input;
1337
- if (!inputObj?.file_path) return null;
1338
- return {
1339
- type: "write",
1340
- data: {
1341
- file_path: inputObj.file_path,
1342
- content: inputObj.content
1343
- }
1344
- };
1345
- },
1346
- parseResult: (content) => ({
1347
- type: "write_result",
1348
- data: { output: content }
1349
- })
1350
- },
1351
- "WebFetch": {
1352
- name: "WebFetch",
1353
- displayName: "Web Fetch",
1354
- iconName: "Globe",
1355
- category: "system",
1356
- consumesResult: true,
1357
- parseInput: (input) => {
1358
- const inputObj = input;
1359
- if (!inputObj?.url) return null;
1360
- return {
1361
- type: "web_fetch",
1362
- data: {
1363
- url: inputObj.url,
1364
- prompt: inputObj.prompt
1365
- }
1366
- };
1367
- },
1368
- parseResult: (content) => ({
1369
- type: "web_fetch_result",
1370
- data: { response: content }
1371
- })
1372
- },
1373
- "mcp__jade__request_status": {
1374
- name: "mcp__jade__request_status",
1375
- displayName: "Request Status",
1376
- iconName: "Loader",
1377
- category: "utility",
1378
- consumesResult: true,
1379
- parseInput: (input) => {
1380
- const inputObj = input;
1381
- return {
1382
- type: "request_status_check",
1383
- data: { requestId: inputObj?.request_id }
1384
- };
1385
- },
1386
- parseResult: (content) => {
1387
- const mediaInfo = extractMediaInfoFromToolResult(content);
1388
- if (mediaInfo) {
1389
- return {
1390
- type: "request_status_completed",
1391
- data: { mediaInfo }
1392
- };
1393
- }
1394
- try {
1395
- const parsed = parseToolResultContent(content);
1396
- if (parsed?.request_id && parsed?.status === "processing") {
1397
- return {
1398
- type: "request_status_processing",
1399
- data: { requestId: parsed.request_id }
1400
- };
1401
- }
1402
- } catch {
1403
- }
1404
- return null;
1405
- }
1406
- },
1407
- "mcp__jade__generative_audio": {
1408
- name: "mcp__jade__generative_audio",
1409
- displayName: "Generative Audio",
1410
- iconName: "AudioLines",
1411
- iconVariant: (input) => {
1412
- const inputObj = input;
1413
- const mode = inputObj?.mode;
1414
- if (mode === "narration") return "Speech";
1415
- if (mode === "sound_effects") return "AudioLines";
1416
- if (mode === "music") return "Music";
1417
- return "AudioLines";
1418
- },
1419
- category: "generation",
1420
- consumesResult: true,
1421
- parseInput: (input) => {
1422
- const inputObj = input;
1423
- return {
1424
- type: "audio_generation",
1425
- data: {
1426
- textInput: inputObj?.text_input,
1427
- videoUrl: inputObj?.video_url,
1428
- mode: inputObj?.mode,
1429
- duration: inputObj?.duration
1430
- }
1431
- };
1432
- },
1433
- parseResult: createMediaParseResult("audio_generation_complete")
1434
- },
1435
- "mcp__jade__generative_image": {
1436
- name: "mcp__jade__generative_image",
1437
- displayName: "Generative Image",
1438
- iconName: "Image",
1439
- category: "generation",
1440
- consumesResult: true,
1441
- parseInput: (input) => {
1442
- const inputObj = input;
1443
- const additionalUrls = inputObj?.additional_image_urls ? typeof inputObj.additional_image_urls === "string" ? inputObj.additional_image_urls.split(",").map((url) => url.trim()).filter(Boolean) : inputObj.additional_image_urls : [];
1444
- const inputImageUrls = [
1445
- inputObj?.image_url,
1446
- ...additionalUrls
1447
- ].filter((url) => url && url !== "");
1448
- return {
1449
- type: "image_generation",
1450
- data: {
1451
- prompt: inputObj?.prompt,
1452
- inputImageUrls,
1453
- model: inputObj?.model,
1454
- aspectRatio: inputObj?.aspect_ratio,
1455
- numImages: inputObj?.num_images
1456
- }
1457
- };
1458
- },
1459
- parseResult: createMediaParseResult("image_generation_complete")
1460
- },
1461
- "mcp__jade__generative_video": {
1462
- name: "mcp__jade__generative_video",
1463
- displayName: "Generative Video",
1464
- iconName: "Video",
1465
- category: "generation",
1466
- consumesResult: true,
1467
- parseInput: (input) => {
1468
- const inputObj = input;
1469
- const inputMediaUrls = [];
1470
- if (inputObj?.input_url) {
1471
- inputMediaUrls.push(inputObj.input_url);
1472
- }
1473
- const lastFrameUrl = inputObj?.last_frame_url;
1474
- if (lastFrameUrl) {
1475
- inputMediaUrls.push(lastFrameUrl);
1476
- }
1477
- const elements = inputObj?.elements;
1478
- if (elements && Array.isArray(elements)) {
1479
- for (const element of elements) {
1480
- if (Array.isArray(element)) {
1481
- for (const url of element) {
1482
- if (url && typeof url === "string") {
1483
- inputMediaUrls.push(url);
1484
- }
1485
- }
1486
- }
1487
- }
1488
- }
1489
- return {
1490
- type: "video_generation",
1491
- data: {
1492
- prompt: inputObj?.prompt,
1493
- inputMediaUrls,
1494
- lastFrameUrl,
1495
- elements,
1496
- model: inputObj?.model_id,
1497
- aspectRatio: inputObj?.aspect_ratio,
1498
- numVideos: inputObj?.num_videos || 1
1499
- }
1500
- };
1501
- },
1502
- parseResult: createMediaParseResult("video_generation_complete")
1503
- },
1504
- "mcp__jade__background_removal": {
1505
- name: "mcp__jade__background_removal",
1506
- displayName: "Background Removal",
1507
- iconName: "ImageOff",
1508
- category: "processing",
1509
- consumesResult: true,
1510
- parseInput: (input) => {
1511
- const inputObj = input;
1512
- return {
1513
- type: "background_removal",
1514
- data: { inputUrl: inputObj?.input_url }
1515
- };
1516
- },
1517
- parseResult: createMediaParseResult("background_removal_complete")
1518
- },
1519
- "mcp__jade__upscale": {
1520
- name: "mcp__jade__upscale",
1521
- displayName: "Upscale",
1522
- iconName: "Maximize2",
1523
- category: "processing",
1524
- consumesResult: true,
1525
- parseInput: (input) => {
1526
- const inputObj = input;
1527
- return {
1528
- type: "upscale",
1529
- data: {
1530
- inputUrl: inputObj?.input_url,
1531
- upscaleFactor: inputObj?.upscale_factor,
1532
- targetResolution: inputObj?.target_resolution
1533
- }
1534
- };
1535
- },
1536
- parseResult: createMediaParseResult("upscale_complete")
1537
- },
1538
- "mcp__jade__generative_character": {
1539
- name: "mcp__jade__generative_character",
1540
- displayName: "Generative Character",
1541
- iconName: "SquareUser",
1542
- category: "generation",
1543
- consumesResult: true,
1544
- parseInput: (input) => {
1545
- const inputObj = input;
1546
- return {
1547
- type: "character_generation",
1548
- data: {
1549
- inputImageUrl: inputObj?.image_url,
1550
- inputAudioUrl: inputObj?.audio_url
1551
- }
1552
- };
1553
- },
1554
- parseResult: createMediaParseResult("character_generation_complete")
1555
- },
1556
- "mcp__jade__import_media": {
1557
- name: "mcp__jade__import_media",
1558
- displayName: "Import Media",
1559
- iconName: "CloudDownload",
1560
- category: "utility",
1561
- consumesResult: true,
1562
- parseInput: (input) => {
1563
- const inputObj = input;
1564
- return {
1565
- type: "media_import",
1566
- data: { source: inputObj?.source }
1567
- };
1568
- },
1569
- parseResult: createMediaParseResult("media_import_complete")
1570
- },
1571
- "mcp__jade__captions_highlights": {
1572
- name: "mcp__jade__captions_highlights",
1573
- displayName: "Captions & Highlights",
1574
- iconName: "Captions",
1575
- category: "processing",
1576
- consumesResult: true,
1577
- parseInput: (input) => {
1578
- const inputObj = input;
1579
- return {
1580
- type: "captions_highlights",
1581
- data: {
1582
- inputUrl: inputObj?.input_url,
1583
- style: inputObj?.style
1584
- }
1585
- };
1586
- },
1587
- parseResult: createMediaParseResult("captions_highlights_complete")
1588
- },
1589
- "mcp__olive__save_skill": {
1590
- name: "mcp__olive__save_skill",
1591
- displayName: "Save Skill",
1592
- iconName: "BookPlus",
1593
- category: "utility",
1594
- consumesResult: true,
1595
- parseInput: (input) => {
1596
- const inputObj = input;
1597
- return {
1598
- type: "save_skill",
1599
- data: {
1600
- source: inputObj?.source,
1601
- name: inputObj?.name,
1602
- scope: inputObj?.scope || "personal",
1603
- edit: inputObj?.edit
1604
- }
1605
- };
1606
- },
1607
- parseResult: (content) => {
1608
- const parsed = parseToolResultContent(content);
1609
- return {
1610
- type: "save_skill_result",
1611
- data: {
1612
- name: parsed?.name,
1613
- description: parsed?.description,
1614
- scope: parsed?.scope,
1615
- action: parsed?.action,
1616
- hasElements: parsed?.has_elements,
1617
- sourceType: parsed?.source_type,
1618
- sizeKb: parsed?.size_kb,
1619
- elements: parsed?.elements || []
1620
- }
1621
- };
1622
- }
1623
- }
1624
- };
1625
- function getToolDefinition(toolName) {
1626
- return TOOL_REGISTRY[toolName];
1627
- }
1628
-
1629
- // src/jade/processing/suggestion-parser.ts
1630
- var SUGGESTION_REGEX = /<gr3\.suggestion>([\s\S]*?)<\/gr3\.suggestion>/g;
1631
- function parseSuggestions(text) {
1632
- const suggestions = [];
1633
- const segments = [];
1634
- let lastIndex = 0;
1635
- let cleanText = "";
1636
- let match;
1637
- const regex = new RegExp(SUGGESTION_REGEX.source, "g");
1638
- while ((match = regex.exec(text)) !== null) {
1639
- const suggestionText = match[1].trim();
1640
- const matchStart = match.index;
1641
- const matchEnd = match.index + match[0].length;
1642
- if (matchStart > lastIndex) {
1643
- const textBefore = text.slice(lastIndex, matchStart);
1644
- segments.push({ type: "text", content: textBefore });
1645
- cleanText += textBefore;
1646
- }
1647
- const startIndex = cleanText.length;
1648
- cleanText += suggestionText;
1649
- const endIndex = cleanText.length;
1650
- suggestions.push({
1651
- text: suggestionText,
1652
- startIndex,
1653
- endIndex
1654
- });
1655
- segments.push({ type: "suggestion", content: suggestionText });
1656
- lastIndex = matchEnd;
1657
- }
1658
- if (lastIndex < text.length) {
1659
- const remainingText = text.slice(lastIndex);
1660
- segments.push({ type: "text", content: remainingText });
1661
- cleanText += remainingText;
1662
- }
1663
- return { cleanText, suggestions, segments };
1664
- }
1665
- function hasSuggestions(text) {
1666
- return SUGGESTION_REGEX.test(text);
1667
- }
1668
-
1669
- // src/jade/processing/conversation-processor.ts
1670
- function processConversation(conversation, options = {}) {
1671
- const {
1672
- skipSkillContext = true,
1673
- pairToolResults = true,
1674
- extractMedia: _extractMedia = true
1675
- } = options;
1676
- const processed = [];
1677
- const consumed = /* @__PURE__ */ new Set();
1678
- const resultsByToolUseId = /* @__PURE__ */ new Map();
1679
- const mediaUrlsByRequestId = /* @__PURE__ */ new Map();
1680
- const metadataByRequestId = /* @__PURE__ */ new Map();
1681
- for (let i = 0; i < conversation.length; i++) {
1682
- const entry = conversation[i];
1683
- if (entry.type === "tool_result" && entry.tool_use_id) {
1684
- resultsByToolUseId.set(entry.tool_use_id, i);
1685
- if (typeof entry.content === "string") {
1686
- const parsed = parseToolResultContent(entry.content);
1687
- if (parsed?.request_id && parsed?.status === "completed" && Array.isArray(parsed?.output_cdn_urls) && parsed.output_cdn_urls.length > 0) {
1688
- mediaUrlsByRequestId.set(parsed.request_id, parsed.output_cdn_urls);
1689
- }
1690
- if (parsed?.request_id && parsed?.status === "completed" && Array.isArray(parsed?.urls) && parsed.urls.length > 0) {
1691
- mediaUrlsByRequestId.set(parsed.request_id, parsed.urls);
1692
- }
1693
- }
1694
- }
1695
- }
1696
- for (let i = 0; i < conversation.length; i++) {
1697
- if (consumed.has(i)) continue;
1698
- const entry = conversation[i];
1699
- if (entry.type === "tool_call" && entry.tool_name) {
1700
- const toolDef = getToolDefinition(entry.tool_name);
1701
- const parsedInput = toolDef?.parseInput?.(entry.tool_input) || null;
1702
- let parsedResult = null;
1703
- if (pairToolResults && toolDef?.consumesResult && toolDef.parseResult && entry.tool_use_id) {
1704
- const resultIndex = resultsByToolUseId.get(entry.tool_use_id);
1705
- if (resultIndex !== void 0) {
1706
- const resultEntry = conversation[resultIndex];
1707
- if (resultEntry.type === "tool_result" && typeof resultEntry.content === "string") {
1708
- parsedResult = toolDef.parseResult(resultEntry.content);
1709
- consumed.add(resultIndex);
1710
- const immediateResult = parseToolResultContent(resultEntry.content);
1711
- const requestId = immediateResult?.request_id;
1712
- if (requestId) {
1713
- const isGenerativeTool = entry.tool_name?.includes("generative_");
1714
- if (isGenerativeTool && entry.tool_use_id && parsedInput?.data) {
1715
- metadataByRequestId.set(requestId, {
1716
- toolUseId: entry.tool_use_id,
1717
- toolName: entry.tool_name,
1718
- prompt: parsedInput.data.prompt,
1719
- model: parsedInput.data.model,
1720
- aspectRatio: parsedInput.data.aspectRatio,
1721
- inputMediaUrls: parsedInput.data.inputMediaUrls || parsedInput.data.inputImageUrls,
1722
- lastFrameUrl: parsedInput.data.lastFrameUrl,
1723
- elements: parsedInput.data.elements
1724
- });
1725
- }
1726
- const eventualUrls = mediaUrlsByRequestId.get(requestId);
1727
- if (eventualUrls) {
1728
- if (!parsedResult) {
1729
- const mediaType = entry.tool_name?.includes("video") ? "video" : entry.tool_name?.includes("audio") ? "audio" : "image";
1730
- parsedResult = {
1731
- type: `${mediaType}_generation_complete`,
1732
- data: { mediaInfo: { urls: eventualUrls, requestId } }
1733
- };
1734
- } else if (!parsedResult.data?.mediaInfo) {
1735
- parsedResult.data.mediaInfo = { urls: eventualUrls, requestId };
1736
- }
1737
- }
1738
- }
1739
- if (toolDef.consumeExtra && toolDef.consumeExtra > 0) {
1740
- for (let j = 1; j <= toolDef.consumeExtra; j++) {
1741
- const extraIndex = resultIndex + j;
1742
- if (extraIndex < conversation.length) {
1743
- consumed.add(extraIndex);
1744
- }
1745
- }
1746
- }
1747
- if (parsedResult?.type === "request_status_completed") {
1748
- const mediaInfo = parsedResult.data?.mediaInfo;
1749
- if (mediaInfo?.requestId) {
1750
- const originalMetadata = metadataByRequestId.get(mediaInfo.requestId);
1751
- if (originalMetadata) {
1752
- mediaInfo.originalToolUseId = originalMetadata.toolUseId;
1753
- mediaInfo.originalToolName = originalMetadata.toolName;
1754
- mediaInfo.prompt = originalMetadata.prompt;
1755
- mediaInfo.model = originalMetadata.model;
1756
- mediaInfo.aspectRatio = originalMetadata.aspectRatio;
1757
- mediaInfo.inputMediaUrls = originalMetadata.inputMediaUrls;
1758
- mediaInfo.lastFrameUrl = originalMetadata.lastFrameUrl;
1759
- mediaInfo.elements = originalMetadata.elements;
1760
- }
1761
- }
1762
- }
1763
- }
1764
- }
1765
- }
1766
- processed.push({
1767
- entry,
1768
- originalType: "tool_call",
1769
- displayType: parsedInput?.type || "unknown_tool",
1770
- iconName: toolDef?.iconName || "Wrench",
1771
- iconData: entry.tool_input,
1772
- title: toolDef && entry.tool_name.startsWith("mcp__jade__") ? toolDef.displayName : void 0,
1773
- data: {
1774
- toolName: entry.tool_name,
1775
- toolUseId: entry.tool_use_id,
1776
- input: parsedInput,
1777
- result: parsedResult
1778
- },
1779
- timestamp: entry.timestamp,
1780
- toolDefinition: toolDef,
1781
- parsedInput: parsedInput || void 0,
1782
- parsedResult: parsedResult || void 0
1783
- });
1784
- continue;
1785
- }
1786
- if (entry.type === "tool_result" && typeof entry.content === "string") {
1787
- try {
1788
- const parsed = parseToolResultContent(entry.content);
1789
- if (parsed?.request_id && parsed?.status && parsed?.message) {
1790
- processed.push({
1791
- entry,
1792
- originalType: "tool_result",
1793
- displayType: "simple_message",
1794
- iconName: "Wrench",
1795
- data: { message: parsed.message },
1796
- timestamp: entry.timestamp
1797
- });
1798
- continue;
1799
- }
1800
- } catch {
1801
- }
1802
- const mediaInfo = extractMediaInfo(entry.content);
1803
- if (mediaInfo) {
1804
- processed.push({
1805
- entry,
1806
- originalType: "tool_result",
1807
- displayType: "media_carousel",
1808
- iconName: "Wrench",
1809
- data: {
1810
- mediaInfo: {
1811
- requestId: mediaInfo.requestId,
1812
- urls: mediaInfo.urls
1813
- }
1814
- },
1815
- timestamp: entry.timestamp
1816
- });
1817
- continue;
1818
- }
1819
- processed.push({
1820
- entry,
1821
- originalType: "tool_result",
1822
- displayType: "raw_content",
1823
- iconName: "Wrench",
1824
- data: { content: entry.content },
1825
- timestamp: entry.timestamp
1826
- });
1827
- continue;
1828
- }
1829
- if (skipSkillContext) {
1830
- if (entry.type === "user_text" && entry.text?.startsWith("Base directory for this skill:")) {
1831
- continue;
1832
- }
1833
- if (entry.type === "user_text" && entry.text?.startsWith("Caveat:")) {
1834
- continue;
1835
- }
1836
- }
1837
- if (entry.type === "user_text" && entry.text) {
1838
- const isCompactSummary = entry.text.startsWith("This session is being continued from a previous conversation");
1839
- if (isCompactSummary) {
1840
- processed.push({
1841
- entry,
1842
- originalType: "compact",
1843
- displayType: "compact",
1844
- iconName: "Sparkles",
1845
- data: {
1846
- isCompacting: false,
1847
- summary: entry.text
1848
- },
1849
- timestamp: entry.timestamp
1850
- });
1851
- continue;
1852
- }
1853
- processed.push({
1854
- entry,
1855
- originalType: "user_text",
1856
- displayType: "user_text",
1857
- iconName: "User",
1858
- data: { text: entry.text },
1859
- timestamp: entry.timestamp
1860
- });
1861
- continue;
1862
- }
1863
- if (entry.type === "assistant_text" && entry.text) {
1864
- const processedEntry = {
1865
- entry,
1866
- originalType: "assistant_text",
1867
- displayType: "markdown_text",
1868
- iconName: "Bot",
1869
- data: { text: entry.text },
1870
- timestamp: entry.timestamp
1871
- };
1872
- if (hasSuggestions(entry.text)) {
1873
- const { cleanText, suggestions, segments } = parseSuggestions(entry.text);
1874
- processedEntry.data = { text: cleanText };
1875
- processedEntry.suggestions = suggestions;
1876
- processedEntry.textSegments = segments;
1877
- }
1878
- processed.push(processedEntry);
1879
- continue;
1880
- }
1881
- if (entry.type === "compact_boundary") {
1882
- const nextEntry = conversation[i + 1];
1883
- const isNextSummary = nextEntry?.type === "compact_summary" || nextEntry?.type === "user_text" && nextEntry.text?.startsWith("This session is being continued from a previous conversation");
1884
- if (isNextSummary) {
1885
- consumed.add(i + 1);
1886
- processed.push({
1887
- entry,
1888
- originalType: "compact",
1889
- displayType: "compact",
1890
- iconName: "Sparkles",
1891
- data: {
1892
- isCompacting: false,
1893
- summary: nextEntry.text || ""
1894
- },
1895
- timestamp: entry.timestamp
1896
- });
1897
- } else {
1898
- processed.push({
1899
- entry,
1900
- originalType: "compact",
1901
- displayType: "compact",
1902
- iconName: "Sparkles",
1903
- data: {
1904
- isCompacting: true,
1905
- summary: null
1906
- },
1907
- timestamp: entry.timestamp
1908
- });
1909
- }
1910
- continue;
1911
- }
1912
- if (entry.type === "compact_summary") {
1913
- processed.push({
1914
- entry,
1915
- originalType: "compact",
1916
- displayType: "compact",
1917
- iconName: "Sparkles",
1918
- data: {
1919
- isCompacting: false,
1920
- summary: entry.text || ""
1921
- },
1922
- timestamp: entry.timestamp
1923
- });
1924
- continue;
1925
- }
1926
- if (entry.type === "error") {
1927
- processed.push({
1928
- entry,
1929
- originalType: "error",
1930
- displayType: "error",
1931
- iconName: "AlertTriangle",
1932
- data: {
1933
- text: entry.text || "Unknown error"
1934
- },
1935
- timestamp: entry.timestamp
1936
- });
1937
- continue;
1938
- }
1939
- }
1940
- return processed;
1941
- }
1942
-
1943
- // src/jade/processing/media-utils.ts
1944
- var ACCEPTED_EXTENSIONS = {
1945
- image: [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".svg"],
1946
- video: [".mp4", ".mov", ".webm", ".avi", ".mkv", ".m4v"],
1947
- audio: [".mp3", ".wav", ".m4a", ".aac", ".ogg", ".oga", ".mpeg"],
1948
- document: [".pdf"]
1949
- };
1950
- function getMediaTypeFromExtension(filename) {
1951
- const normalizedFilename = filename.toLowerCase();
1952
- for (const [type, extensions] of Object.entries(ACCEPTED_EXTENSIONS)) {
1953
- if (extensions.some((ext) => normalizedFilename.endsWith(ext))) {
1954
- return type;
1955
- }
1956
- }
1957
- return null;
1958
- }
1959
- function getMediaTypeFromUrl(url) {
1960
- try {
1961
- const urlObj = new URL(url);
1962
- const pathname = urlObj.pathname;
1963
- const filename = pathname.split("/").pop() || "";
1964
- return getMediaTypeFromExtension(filename);
1965
- } catch {
1966
- const cleanUrl = url.split("?")[0];
1967
- return getMediaTypeFromExtension(cleanUrl);
1968
- }
1969
- }
1970
-
1971
- // src/jade/processing/media-extractor.ts
1972
- function extractMedia(conversation) {
1973
- const urlMap = /* @__PURE__ */ new Map();
1974
- const processed = processConversation(conversation);
1975
- for (const entry of processed) {
1976
- if (entry.originalType === "tool_call") {
1977
- const data = entry.data;
1978
- const result = data?.result;
1979
- const resultData = result?.data;
1980
- const mediaInfo = resultData?.mediaInfo;
1981
- const urls = mediaInfo?.urls;
1982
- if (urls && Array.isArray(urls)) {
1983
- const input = data?.input;
1984
- const inputData = input?.data;
1985
- const toolName = data?.toolName;
1986
- const toolUseId = data?.toolUseId;
1987
- for (const url of urls) {
1988
- if (typeof url !== "string") continue;
1989
- const detectedType = getMediaTypeFromUrl(url);
1990
- const type = detectedType === "video" ? "video" : detectedType === "audio" ? "audio" : "image";
1991
- const existing = urlMap.get(url);
1992
- const hasInputMetadata = !!(inputData?.prompt || inputData?.model);
1993
- const existingHasMetadata = !!(existing?.prompt || existing?.model);
1994
- if (!existing || hasInputMetadata && !existingHasMetadata) {
1995
- urlMap.set(url, {
1996
- url,
1997
- type,
1998
- timestamp: entry.timestamp,
1999
- toolName,
2000
- toolUseId,
2001
- prompt: inputData?.prompt,
2002
- model: inputData?.model,
2003
- aspectRatio: inputData?.aspectRatio,
2004
- inputMediaUrls: inputData?.inputImageUrls || inputData?.inputMediaUrls
2005
- });
2006
- }
2007
- }
2008
- continue;
2009
- }
2010
- }
2011
- if (entry.originalType === "tool_result" && entry.displayType === "media_carousel") {
2012
- const data = entry.data;
2013
- const mediaInfo = data?.mediaInfo;
2014
- const urls = mediaInfo?.urls;
2015
- if (urls && Array.isArray(urls)) {
2016
- for (const url of urls) {
2017
- if (typeof url !== "string") continue;
2018
- const detectedType = getMediaTypeFromUrl(url);
2019
- const type = detectedType === "video" ? "video" : detectedType === "audio" ? "audio" : "image";
2020
- if (!urlMap.has(url)) {
2021
- urlMap.set(url, {
2022
- url,
2023
- type,
2024
- timestamp: entry.timestamp
2025
- });
2026
- }
2027
- }
2028
- }
2029
- }
2030
- }
2031
- const mediaArray = Array.from(urlMap.values());
2032
- mediaArray.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
2033
- return mediaArray;
2034
- }
2035
-
2036
- // src/jade/react/use-jade-session.ts
2037
- function useJadeSession(options = {}) {
2038
- const {
2039
- skipSkillContext = true,
2040
- processingOptions,
2041
- ...agentOptions
2042
- } = options;
2043
- const session = useAgentSession(agentOptions);
2044
- const processedConversation = useMemo(
2045
- () => processConversation(session.conversation, {
2046
- skipSkillContext,
2047
- pairToolResults: true,
2048
- extractMedia: true,
2049
- ...processingOptions
2050
- }),
2051
- [session.conversation, skipSkillContext, processingOptions]
2052
- );
2053
- const media = useMemo(
2054
- () => extractMedia(session.conversation),
2055
- [session.conversation]
2056
- );
2057
- return {
2058
- ...session,
2059
- processedConversation,
2060
- media
2061
- };
2062
- }
2063
- function useMedia(conversation) {
2064
- return useMemo(() => extractMedia(conversation), [conversation]);
2065
- }
2066
-
2067
- // src/jade/react/JadeProvider.tsx
2068
- var JadeProvider = AgentProvider;
2069
-
2070
- export { AgentProvider, JadeProvider, useAgentClient, useAgentSession, useJadeSession, useMedia, useSkills };