@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.
- package/README.md +1 -1
- package/dist/index.cjs +927 -0
- package/dist/index.d.cts +236 -4
- package/dist/index.d.ts +236 -4
- package/dist/index.js +916 -1
- package/package.json +2 -7
- package/dist/react/index.cjs +0 -2082
- package/dist/react/index.d.cts +0 -95
- package/dist/react/index.d.ts +0 -95
- package/dist/react/index.js +0 -2070
- package/dist/types-peBknkiN.d.cts +0 -145
- package/dist/types-peBknkiN.d.ts +0 -145
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var useSWR = require('swr');
|
|
6
|
+
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
|
|
10
|
+
|
|
3
11
|
// ../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1/node_modules/@microsoft/fetch-event-source/lib/esm/parse.js
|
|
4
12
|
async function getBytes(stream, onChunk) {
|
|
5
13
|
const reader = stream.getReader();
|
|
@@ -1673,11 +1681,925 @@ function extractMedia(conversation) {
|
|
|
1673
1681
|
mediaArray.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
1674
1682
|
return mediaArray;
|
|
1675
1683
|
}
|
|
1684
|
+
var AgentEventEmitter2 = class {
|
|
1685
|
+
constructor() {
|
|
1686
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Subscribe to an event.
|
|
1690
|
+
* @returns Unsubscribe function
|
|
1691
|
+
*/
|
|
1692
|
+
on(event, handler) {
|
|
1693
|
+
if (!this.handlers.has(event)) {
|
|
1694
|
+
this.handlers.set(event, /* @__PURE__ */ new Set());
|
|
1695
|
+
}
|
|
1696
|
+
this.handlers.get(event).add(handler);
|
|
1697
|
+
return () => this.off(event, handler);
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Unsubscribe from an event.
|
|
1701
|
+
*/
|
|
1702
|
+
off(event, handler) {
|
|
1703
|
+
this.handlers.get(event)?.delete(handler);
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Emit an event to all handlers.
|
|
1707
|
+
*/
|
|
1708
|
+
emit(event, data) {
|
|
1709
|
+
this.handlers.get(event)?.forEach((handler) => {
|
|
1710
|
+
try {
|
|
1711
|
+
handler(data);
|
|
1712
|
+
} catch (err) {
|
|
1713
|
+
console.error(`[AgentEventEmitter] Error in ${event} handler:`, err);
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Remove all event handlers.
|
|
1719
|
+
*/
|
|
1720
|
+
removeAllListeners() {
|
|
1721
|
+
this.handlers.clear();
|
|
1722
|
+
}
|
|
1723
|
+
};
|
|
1724
|
+
var AgentClientError2 = class extends Error {
|
|
1725
|
+
constructor(message, cause) {
|
|
1726
|
+
super(message);
|
|
1727
|
+
this.cause = cause;
|
|
1728
|
+
this.name = "AgentClientError";
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1731
|
+
var AuthenticationError2 = class extends AgentClientError2 {
|
|
1732
|
+
constructor(message = "Authentication failed") {
|
|
1733
|
+
super(message);
|
|
1734
|
+
this.name = "AuthenticationError";
|
|
1735
|
+
}
|
|
1736
|
+
};
|
|
1737
|
+
var SessionNotFoundError2 = class extends AgentClientError2 {
|
|
1738
|
+
constructor(sessionId) {
|
|
1739
|
+
super(`Session not found: ${sessionId}`);
|
|
1740
|
+
this.sessionId = sessionId;
|
|
1741
|
+
this.name = "SessionNotFoundError";
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
var SkillNotFoundError2 = class extends AgentClientError2 {
|
|
1745
|
+
constructor(skillName) {
|
|
1746
|
+
super(`Skill not found: ${skillName}`);
|
|
1747
|
+
this.skillName = skillName;
|
|
1748
|
+
this.name = "SkillNotFoundError";
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
var RequestError2 = class extends AgentClientError2 {
|
|
1752
|
+
constructor(message, statusCode) {
|
|
1753
|
+
super(message);
|
|
1754
|
+
this.statusCode = statusCode;
|
|
1755
|
+
this.name = "RequestError";
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
var FatalSSEError2 = class extends Error {
|
|
1759
|
+
constructor(message) {
|
|
1760
|
+
super(message);
|
|
1761
|
+
this.name = "FatalSSEError";
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
var MAX_RETRIES2 = 5;
|
|
1765
|
+
var INITIAL_RETRY_DELAY2 = 1e3;
|
|
1766
|
+
var MAX_RETRY_DELAY2 = 1e4;
|
|
1767
|
+
async function createSSEStream2(options) {
|
|
1768
|
+
const {
|
|
1769
|
+
url,
|
|
1770
|
+
method,
|
|
1771
|
+
headers,
|
|
1772
|
+
body,
|
|
1773
|
+
emitter,
|
|
1774
|
+
signal,
|
|
1775
|
+
onOpen,
|
|
1776
|
+
onClose,
|
|
1777
|
+
lastEventId
|
|
1778
|
+
} = options;
|
|
1779
|
+
const processedIds = /* @__PURE__ */ new Set();
|
|
1780
|
+
let retryCount = 0;
|
|
1781
|
+
try {
|
|
1782
|
+
await fetchEventSource(url, {
|
|
1783
|
+
method,
|
|
1784
|
+
headers: {
|
|
1785
|
+
"Content-Type": "application/json",
|
|
1786
|
+
...lastEventId ? { "Last-Event-ID": lastEventId } : {},
|
|
1787
|
+
...headers
|
|
1788
|
+
},
|
|
1789
|
+
body: JSON.stringify(body),
|
|
1790
|
+
signal,
|
|
1791
|
+
async onopen(response) {
|
|
1792
|
+
if (response.ok && response.headers.get("content-type")?.includes(EventStreamContentType)) {
|
|
1793
|
+
onOpen?.();
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
if (response.status === 401 || response.status === 403) {
|
|
1797
|
+
throw new FatalSSEError2(`Authentication failed: ${response.status}`);
|
|
1798
|
+
}
|
|
1799
|
+
if (response.status === 404) {
|
|
1800
|
+
throw new FatalSSEError2("Session not found or not active");
|
|
1801
|
+
}
|
|
1802
|
+
if (response.status === 503) {
|
|
1803
|
+
const text = await response.text().catch(() => "Service unavailable");
|
|
1804
|
+
throw new FatalSSEError2(`Server unavailable: ${text}`);
|
|
1805
|
+
}
|
|
1806
|
+
if (response.status >= 400 && response.status < 500) {
|
|
1807
|
+
const text = await response.text().catch(() => `Error ${response.status}`);
|
|
1808
|
+
throw new FatalSSEError2(text);
|
|
1809
|
+
}
|
|
1810
|
+
throw new Error(`Failed to connect: ${response.status}`);
|
|
1811
|
+
},
|
|
1812
|
+
onmessage(event) {
|
|
1813
|
+
if (event.id && processedIds.has(event.id)) {
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
if (event.id) {
|
|
1817
|
+
processedIds.add(event.id);
|
|
1818
|
+
}
|
|
1819
|
+
if (!event.data) {
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
try {
|
|
1823
|
+
const data = JSON.parse(event.data);
|
|
1824
|
+
const eventType = event.event || "message";
|
|
1825
|
+
switch (eventType) {
|
|
1826
|
+
case "init":
|
|
1827
|
+
case "session":
|
|
1828
|
+
emitter.emit("session", { session_id: data.session_id });
|
|
1829
|
+
break;
|
|
1830
|
+
case "entry":
|
|
1831
|
+
emitter.emit("entry", { entry: data });
|
|
1832
|
+
break;
|
|
1833
|
+
case "stream_event":
|
|
1834
|
+
handleStreamEvent2(data, emitter);
|
|
1835
|
+
break;
|
|
1836
|
+
case "content_block_start":
|
|
1837
|
+
emitter.emit("content_block_start", data);
|
|
1838
|
+
break;
|
|
1839
|
+
case "content_block_delta":
|
|
1840
|
+
emitter.emit("content_block_delta", data);
|
|
1841
|
+
break;
|
|
1842
|
+
case "content_block_stop":
|
|
1843
|
+
emitter.emit("content_block_stop", data);
|
|
1844
|
+
break;
|
|
1845
|
+
case "complete":
|
|
1846
|
+
emitter.emit("complete", data);
|
|
1847
|
+
break;
|
|
1848
|
+
case "error":
|
|
1849
|
+
emitter.emit("error", { error: data.error });
|
|
1850
|
+
break;
|
|
1851
|
+
case "heartbeat":
|
|
1852
|
+
emitter.emit("heartbeat", {});
|
|
1853
|
+
break;
|
|
1854
|
+
default:
|
|
1855
|
+
break;
|
|
1856
|
+
}
|
|
1857
|
+
} catch (e) {
|
|
1858
|
+
console.error("[SSE] Failed to parse event:", event.data, e);
|
|
1859
|
+
}
|
|
1860
|
+
},
|
|
1861
|
+
onerror(err) {
|
|
1862
|
+
if (signal?.aborted) {
|
|
1863
|
+
throw err;
|
|
1864
|
+
}
|
|
1865
|
+
if (err instanceof FatalSSEError2) {
|
|
1866
|
+
emitter.emit("error", { error: err.message });
|
|
1867
|
+
throw err;
|
|
1868
|
+
}
|
|
1869
|
+
retryCount++;
|
|
1870
|
+
if (retryCount > MAX_RETRIES2) {
|
|
1871
|
+
const error = `Connection failed after ${MAX_RETRIES2} retries`;
|
|
1872
|
+
emitter.emit("error", { error });
|
|
1873
|
+
throw new FatalSSEError2(error);
|
|
1874
|
+
}
|
|
1875
|
+
const delay = Math.min(INITIAL_RETRY_DELAY2 * Math.pow(2, retryCount - 1), MAX_RETRY_DELAY2);
|
|
1876
|
+
console.log(`[SSE] Retry ${retryCount}/${MAX_RETRIES2} in ${delay}ms`);
|
|
1877
|
+
return delay;
|
|
1878
|
+
},
|
|
1879
|
+
onclose() {
|
|
1880
|
+
onClose?.();
|
|
1881
|
+
},
|
|
1882
|
+
// Keep connection open when tab is hidden - heartbeats keep it alive
|
|
1883
|
+
openWhenHidden: true
|
|
1884
|
+
});
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
if (error instanceof FatalSSEError2) {
|
|
1887
|
+
if (error.message.includes("Authentication")) {
|
|
1888
|
+
throw new AuthenticationError2(error.message);
|
|
1889
|
+
}
|
|
1890
|
+
throw new RequestError2(error.message, 500);
|
|
1891
|
+
}
|
|
1892
|
+
if (error instanceof Error) {
|
|
1893
|
+
emitter.emit("error", { error: error.message });
|
|
1894
|
+
}
|
|
1895
|
+
throw error;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
function handleStreamEvent2(data, emitter) {
|
|
1899
|
+
const streamEvent = data.event || data;
|
|
1900
|
+
const eventData = streamEvent;
|
|
1901
|
+
switch (eventData.type) {
|
|
1902
|
+
case "content_block_start":
|
|
1903
|
+
emitter.emit("content_block_start", eventData);
|
|
1904
|
+
break;
|
|
1905
|
+
case "content_block_delta":
|
|
1906
|
+
emitter.emit("content_block_delta", eventData);
|
|
1907
|
+
break;
|
|
1908
|
+
case "content_block_stop":
|
|
1909
|
+
emitter.emit("content_block_stop", eventData);
|
|
1910
|
+
break;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
var AgentClient2 = class {
|
|
1914
|
+
constructor(config) {
|
|
1915
|
+
this.activeStreams = /* @__PURE__ */ new Map();
|
|
1916
|
+
this.config = {
|
|
1917
|
+
apiVersion: "/v1",
|
|
1918
|
+
timeout: 3e4,
|
|
1919
|
+
...config
|
|
1920
|
+
};
|
|
1921
|
+
}
|
|
1922
|
+
// ===========================================================================
|
|
1923
|
+
// Public getters
|
|
1924
|
+
// ===========================================================================
|
|
1925
|
+
/** Whether org context is configured (orgId is set) */
|
|
1926
|
+
get hasOrgContext() {
|
|
1927
|
+
return !!this.config.orgId;
|
|
1928
|
+
}
|
|
1929
|
+
// ===========================================================================
|
|
1930
|
+
// Private helpers
|
|
1931
|
+
// ===========================================================================
|
|
1932
|
+
get baseUrl() {
|
|
1933
|
+
return `${this.config.endpoint}${this.config.apiVersion}`;
|
|
1934
|
+
}
|
|
1935
|
+
async getHeaders() {
|
|
1936
|
+
const headers = {
|
|
1937
|
+
"Content-Type": "application/json"
|
|
1938
|
+
};
|
|
1939
|
+
const token = await this.config.getAuthToken();
|
|
1940
|
+
if (token) {
|
|
1941
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
1942
|
+
}
|
|
1943
|
+
return headers;
|
|
1944
|
+
}
|
|
1945
|
+
async request(path, options = {}) {
|
|
1946
|
+
const url = `${this.baseUrl}${path}`;
|
|
1947
|
+
const headers = await this.getHeaders();
|
|
1948
|
+
const controller = new AbortController();
|
|
1949
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
1950
|
+
try {
|
|
1951
|
+
const response = await fetch(url, {
|
|
1952
|
+
...options,
|
|
1953
|
+
headers: { ...headers, ...options.headers || {} },
|
|
1954
|
+
signal: controller.signal
|
|
1955
|
+
});
|
|
1956
|
+
if (!response.ok) {
|
|
1957
|
+
await this.handleErrorResponse(response, path);
|
|
1958
|
+
}
|
|
1959
|
+
return response.json();
|
|
1960
|
+
} finally {
|
|
1961
|
+
clearTimeout(timeoutId);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
async handleErrorResponse(response, path) {
|
|
1965
|
+
if (response.status === 401 || response.status === 403) {
|
|
1966
|
+
throw new AuthenticationError2();
|
|
1967
|
+
}
|
|
1968
|
+
if (response.status === 404) {
|
|
1969
|
+
const text = await response.text();
|
|
1970
|
+
if (text.toLowerCase().includes("session")) {
|
|
1971
|
+
const sessionId = path.split("/").pop() || "";
|
|
1972
|
+
throw new SessionNotFoundError2(sessionId);
|
|
1973
|
+
}
|
|
1974
|
+
if (text.toLowerCase().includes("skill")) {
|
|
1975
|
+
const skillName = path.split("/").pop() || "";
|
|
1976
|
+
throw new SkillNotFoundError2(skillName);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
throw new RequestError2(`Request failed: ${response.status}`, response.status);
|
|
1980
|
+
}
|
|
1981
|
+
// ===========================================================================
|
|
1982
|
+
// Messages API
|
|
1983
|
+
// ===========================================================================
|
|
1984
|
+
/**
|
|
1985
|
+
* Send a message and stream responses.
|
|
1986
|
+
* @returns Object with event emitter and abort function
|
|
1987
|
+
*/
|
|
1988
|
+
stream(request) {
|
|
1989
|
+
const emitter = new AgentEventEmitter2();
|
|
1990
|
+
const controller = new AbortController();
|
|
1991
|
+
const streamId = request.session_id || `stream-${Date.now()}`;
|
|
1992
|
+
this.activeStreams.set(streamId, controller);
|
|
1993
|
+
(async () => {
|
|
1994
|
+
try {
|
|
1995
|
+
const headers = await this.getHeaders();
|
|
1996
|
+
await createSSEStream2({
|
|
1997
|
+
url: `${this.baseUrl}/messages`,
|
|
1998
|
+
method: "POST",
|
|
1999
|
+
headers,
|
|
2000
|
+
body: { ...request, stream: true },
|
|
2001
|
+
emitter,
|
|
2002
|
+
signal: controller.signal
|
|
2003
|
+
});
|
|
2004
|
+
} catch (error) {
|
|
2005
|
+
emitter.emit("error", {
|
|
2006
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2007
|
+
});
|
|
2008
|
+
} finally {
|
|
2009
|
+
this.activeStreams.delete(streamId);
|
|
2010
|
+
}
|
|
2011
|
+
})();
|
|
2012
|
+
return {
|
|
2013
|
+
emitter,
|
|
2014
|
+
abort: () => controller.abort()
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
// ===========================================================================
|
|
2018
|
+
// Sessions API
|
|
2019
|
+
// ===========================================================================
|
|
2020
|
+
/**
|
|
2021
|
+
* List all sessions for the authenticated user.
|
|
2022
|
+
*/
|
|
2023
|
+
async listSessions() {
|
|
2024
|
+
return this.request("/sessions");
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Get session content in display format.
|
|
2028
|
+
*/
|
|
2029
|
+
async getSession(sessionId) {
|
|
2030
|
+
return this.request(`/sessions/${sessionId}`);
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Get session content as raw JSONL.
|
|
2034
|
+
*/
|
|
2035
|
+
async getSessionRaw(sessionId) {
|
|
2036
|
+
return this.request(`/sessions/${sessionId}?format=raw`);
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Check if a session is actively streaming.
|
|
2040
|
+
*/
|
|
2041
|
+
async getSessionStatus(sessionId) {
|
|
2042
|
+
return this.request(`/sessions/${sessionId}/status`);
|
|
2043
|
+
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Update session metadata (name).
|
|
2046
|
+
*/
|
|
2047
|
+
async updateSession(sessionId, updates) {
|
|
2048
|
+
return this.request(`/sessions/${sessionId}`, {
|
|
2049
|
+
method: "PATCH",
|
|
2050
|
+
body: JSON.stringify(updates)
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Delete a session.
|
|
2055
|
+
*/
|
|
2056
|
+
async deleteSession(sessionId) {
|
|
2057
|
+
return this.request(`/sessions/${sessionId}`, {
|
|
2058
|
+
method: "DELETE"
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Cancel an active session query.
|
|
2063
|
+
*/
|
|
2064
|
+
async cancelSession(sessionId) {
|
|
2065
|
+
return this.request(`/sessions/${sessionId}/cancel`, {
|
|
2066
|
+
method: "POST"
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Reconnect to an active streaming session.
|
|
2071
|
+
* @returns Object with event emitter and abort function
|
|
2072
|
+
*/
|
|
2073
|
+
reconnect(sessionId, lastEventId = -1) {
|
|
2074
|
+
const emitter = new AgentEventEmitter2();
|
|
2075
|
+
const controller = new AbortController();
|
|
2076
|
+
(async () => {
|
|
2077
|
+
try {
|
|
2078
|
+
const headers = await this.getHeaders();
|
|
2079
|
+
await createSSEStream2({
|
|
2080
|
+
url: `${this.baseUrl}/sessions/${sessionId}/reconnect`,
|
|
2081
|
+
method: "POST",
|
|
2082
|
+
headers,
|
|
2083
|
+
body: { last_event_id: lastEventId },
|
|
2084
|
+
emitter,
|
|
2085
|
+
signal: controller.signal
|
|
2086
|
+
});
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
emitter.emit("error", {
|
|
2089
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
})();
|
|
2093
|
+
return {
|
|
2094
|
+
emitter,
|
|
2095
|
+
abort: () => controller.abort()
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
// ===========================================================================
|
|
2099
|
+
// Skills API - Personal
|
|
2100
|
+
// ===========================================================================
|
|
2101
|
+
/**
|
|
2102
|
+
* List personal skills.
|
|
2103
|
+
*/
|
|
2104
|
+
async listSkills() {
|
|
2105
|
+
return this.request("/skills");
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Get personal skill content.
|
|
2109
|
+
*/
|
|
2110
|
+
async getSkill(name) {
|
|
2111
|
+
const response = await this.request(`/skills/${name}`);
|
|
2112
|
+
return this.decodeBase64(response.content);
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Create or update a personal skill.
|
|
2116
|
+
*/
|
|
2117
|
+
async saveSkill(skill) {
|
|
2118
|
+
const content = this.encodeBase64(skill.content);
|
|
2119
|
+
return this.request("/skills", {
|
|
2120
|
+
method: "POST",
|
|
2121
|
+
body: JSON.stringify({ ...skill, content })
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Delete a personal skill.
|
|
2126
|
+
*/
|
|
2127
|
+
async deleteSkill(name) {
|
|
2128
|
+
return this.request(`/skills/${name}`, {
|
|
2129
|
+
method: "DELETE"
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
// ===========================================================================
|
|
2133
|
+
// Skills API - Organization
|
|
2134
|
+
// ===========================================================================
|
|
2135
|
+
/**
|
|
2136
|
+
* List organization skills.
|
|
2137
|
+
*/
|
|
2138
|
+
async listOrgSkills() {
|
|
2139
|
+
return this.request("/skills/org");
|
|
2140
|
+
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Get organization skill content.
|
|
2143
|
+
*/
|
|
2144
|
+
async getOrgSkill(name) {
|
|
2145
|
+
const response = await this.request(
|
|
2146
|
+
`/skills/org/${name}`
|
|
2147
|
+
);
|
|
2148
|
+
return this.decodeBase64(response.content);
|
|
2149
|
+
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Create or update an organization skill.
|
|
2152
|
+
*/
|
|
2153
|
+
async saveOrgSkill(skill) {
|
|
2154
|
+
const content = this.encodeBase64(skill.content);
|
|
2155
|
+
return this.request("/skills/org", {
|
|
2156
|
+
method: "POST",
|
|
2157
|
+
body: JSON.stringify({ ...skill, content })
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Delete an organization skill.
|
|
2162
|
+
*/
|
|
2163
|
+
async deleteOrgSkill(name) {
|
|
2164
|
+
return this.request(`/skills/org/${name}`, {
|
|
2165
|
+
method: "DELETE"
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2168
|
+
// ===========================================================================
|
|
2169
|
+
// Plugins API
|
|
2170
|
+
// ===========================================================================
|
|
2171
|
+
/**
|
|
2172
|
+
* Get plugin manifest for client sync.
|
|
2173
|
+
*/
|
|
2174
|
+
async getPluginManifest() {
|
|
2175
|
+
return this.request("/plugins/manifest");
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Download plugin bundle.
|
|
2179
|
+
*/
|
|
2180
|
+
async getPluginBundle(skills) {
|
|
2181
|
+
const query = skills?.length ? `?skills=${skills.join(",")}` : "";
|
|
2182
|
+
return this.request(`/plugins/bundle${query}`);
|
|
2183
|
+
}
|
|
2184
|
+
// ===========================================================================
|
|
2185
|
+
// Utility methods
|
|
2186
|
+
// ===========================================================================
|
|
2187
|
+
encodeBase64(data) {
|
|
2188
|
+
if (typeof data === "string") {
|
|
2189
|
+
const encoder = new TextEncoder();
|
|
2190
|
+
data = encoder.encode(data);
|
|
2191
|
+
}
|
|
2192
|
+
return btoa(String.fromCharCode(...data));
|
|
2193
|
+
}
|
|
2194
|
+
decodeBase64(base64) {
|
|
2195
|
+
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
2196
|
+
}
|
|
2197
|
+
};
|
|
2198
|
+
var AgentContext = react.createContext(null);
|
|
2199
|
+
function AgentProvider({ children, config }) {
|
|
2200
|
+
const client = react.useMemo(
|
|
2201
|
+
() => new AgentClient2(config),
|
|
2202
|
+
// Only recreate client if endpoint changes
|
|
2203
|
+
// getAuthToken is a function and should not trigger recreation
|
|
2204
|
+
[config.endpoint, config.apiVersion, config.timeout]
|
|
2205
|
+
);
|
|
2206
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AgentContext.Provider, { value: { client }, children });
|
|
2207
|
+
}
|
|
2208
|
+
function useAgentClient() {
|
|
2209
|
+
const context = react.useContext(AgentContext);
|
|
2210
|
+
if (!context) {
|
|
2211
|
+
throw new Error("useAgentClient must be used within an AgentProvider");
|
|
2212
|
+
}
|
|
2213
|
+
return context.client;
|
|
2214
|
+
}
|
|
2215
|
+
function useAgentSession(options = {}) {
|
|
2216
|
+
const client = useAgentClient();
|
|
2217
|
+
const { initialSessionId, initialConversation, onMediaGenerated } = options;
|
|
2218
|
+
const [session, setSession] = react.useState({
|
|
2219
|
+
sessionId: initialSessionId,
|
|
2220
|
+
conversation: initialConversation ?? [],
|
|
2221
|
+
isStreaming: false
|
|
2222
|
+
});
|
|
2223
|
+
const abortRef = react.useRef(null);
|
|
2224
|
+
const accumulatedTextRef = react.useRef({});
|
|
2225
|
+
const setupEventHandlers = react.useCallback(
|
|
2226
|
+
(emitter) => {
|
|
2227
|
+
emitter.on("session", (data) => {
|
|
2228
|
+
setSession((prev) => ({ ...prev, sessionId: data.session_id }));
|
|
2229
|
+
});
|
|
2230
|
+
emitter.on("content_block_start", (data) => {
|
|
2231
|
+
const block = data.content_block;
|
|
2232
|
+
if (block?.type === "text") {
|
|
2233
|
+
accumulatedTextRef.current[data.index] = "";
|
|
2234
|
+
setSession((prev) => ({ ...prev, showTinkering: false }));
|
|
2235
|
+
} else if (block?.type === "tool_use") {
|
|
2236
|
+
setSession((prev) => ({
|
|
2237
|
+
...prev,
|
|
2238
|
+
streamingToolCall: {
|
|
2239
|
+
tool_name: block.name || "",
|
|
2240
|
+
tool_use_id: block.id || "",
|
|
2241
|
+
block_index: data.index,
|
|
2242
|
+
accumulated_input: ""
|
|
2243
|
+
},
|
|
2244
|
+
showTinkering: false
|
|
2245
|
+
}));
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
emitter.on("content_block_delta", (data) => {
|
|
2249
|
+
const delta = data.delta;
|
|
2250
|
+
if (delta?.type === "text_delta" && delta.text) {
|
|
2251
|
+
const idx = data.index ?? 0;
|
|
2252
|
+
accumulatedTextRef.current[idx] = (accumulatedTextRef.current[idx] || "") + delta.text;
|
|
2253
|
+
setSession((prev) => ({
|
|
2254
|
+
...prev,
|
|
2255
|
+
streamingText: accumulatedTextRef.current[idx],
|
|
2256
|
+
streamingBlockIndex: idx,
|
|
2257
|
+
showTinkering: false
|
|
2258
|
+
}));
|
|
2259
|
+
} else if (delta?.type === "input_json_delta" && delta.partial_json) {
|
|
2260
|
+
setSession((prev) => {
|
|
2261
|
+
if (!prev.streamingToolCall) return prev;
|
|
2262
|
+
return {
|
|
2263
|
+
...prev,
|
|
2264
|
+
streamingToolCall: {
|
|
2265
|
+
...prev.streamingToolCall,
|
|
2266
|
+
accumulated_input: (prev.streamingToolCall.accumulated_input || "") + delta.partial_json
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
});
|
|
2272
|
+
emitter.on("content_block_stop", (data) => {
|
|
2273
|
+
const idx = data.index ?? 0;
|
|
2274
|
+
if (accumulatedTextRef.current[idx] !== void 0) {
|
|
2275
|
+
delete accumulatedTextRef.current[idx];
|
|
2276
|
+
setSession((prev) => ({
|
|
2277
|
+
...prev,
|
|
2278
|
+
streamingText: void 0,
|
|
2279
|
+
streamingBlockIndex: void 0
|
|
2280
|
+
}));
|
|
2281
|
+
}
|
|
2282
|
+
});
|
|
2283
|
+
emitter.on("entry", (data) => {
|
|
2284
|
+
const entry = data.entry;
|
|
2285
|
+
if (entry.type === "tool_result" && onMediaGenerated) {
|
|
2286
|
+
const media = extractMediaFromEntry(entry);
|
|
2287
|
+
if (media) {
|
|
2288
|
+
onMediaGenerated(media.urls, media.type);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
if (entry.type === "tool_call") {
|
|
2292
|
+
setSession((prev) => ({
|
|
2293
|
+
...prev,
|
|
2294
|
+
conversation: [...prev.conversation, entry],
|
|
2295
|
+
streamingToolCall: void 0
|
|
2296
|
+
}));
|
|
2297
|
+
} else {
|
|
2298
|
+
setSession((prev) => ({
|
|
2299
|
+
...prev,
|
|
2300
|
+
conversation: [...prev.conversation, entry]
|
|
2301
|
+
}));
|
|
2302
|
+
}
|
|
2303
|
+
});
|
|
2304
|
+
emitter.on("complete", (data) => {
|
|
2305
|
+
setSession((prev) => ({
|
|
2306
|
+
...prev,
|
|
2307
|
+
sessionId: data.session_id || prev.sessionId,
|
|
2308
|
+
isStreaming: false,
|
|
2309
|
+
showTinkering: false
|
|
2310
|
+
}));
|
|
2311
|
+
abortRef.current = null;
|
|
2312
|
+
});
|
|
2313
|
+
emitter.on("error", (data) => {
|
|
2314
|
+
setSession((prev) => ({
|
|
2315
|
+
...prev,
|
|
2316
|
+
conversation: [
|
|
2317
|
+
...prev.conversation,
|
|
2318
|
+
{
|
|
2319
|
+
type: "assistant_text",
|
|
2320
|
+
text: `Error: ${data.error}`,
|
|
2321
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2322
|
+
}
|
|
2323
|
+
],
|
|
2324
|
+
isStreaming: false,
|
|
2325
|
+
showTinkering: false
|
|
2326
|
+
}));
|
|
2327
|
+
abortRef.current = null;
|
|
2328
|
+
});
|
|
2329
|
+
},
|
|
2330
|
+
[onMediaGenerated]
|
|
2331
|
+
);
|
|
2332
|
+
const sendMessage = react.useCallback(
|
|
2333
|
+
async (prompt, skills) => {
|
|
2334
|
+
if (!prompt.trim() || session.isStreaming) return;
|
|
2335
|
+
const userEntry = {
|
|
2336
|
+
type: "user_text",
|
|
2337
|
+
text: prompt,
|
|
2338
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2339
|
+
};
|
|
2340
|
+
setSession((prev) => ({
|
|
2341
|
+
...prev,
|
|
2342
|
+
conversation: [...prev.conversation, userEntry],
|
|
2343
|
+
isStreaming: true,
|
|
2344
|
+
showTinkering: true
|
|
2345
|
+
}));
|
|
2346
|
+
accumulatedTextRef.current = {};
|
|
2347
|
+
const { emitter, abort } = client.stream({
|
|
2348
|
+
prompt,
|
|
2349
|
+
session_id: session.sessionId,
|
|
2350
|
+
skills
|
|
2351
|
+
});
|
|
2352
|
+
abortRef.current = abort;
|
|
2353
|
+
setupEventHandlers(emitter);
|
|
2354
|
+
},
|
|
2355
|
+
[client, session.sessionId, session.isStreaming, setupEventHandlers]
|
|
2356
|
+
);
|
|
2357
|
+
const cancel = react.useCallback(async () => {
|
|
2358
|
+
abortRef.current?.();
|
|
2359
|
+
abortRef.current = null;
|
|
2360
|
+
if (session.sessionId) {
|
|
2361
|
+
try {
|
|
2362
|
+
await client.cancelSession(session.sessionId);
|
|
2363
|
+
} catch (error) {
|
|
2364
|
+
console.error("[Cancel] Server-side cancel failed:", error);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
setSession((prev) => ({
|
|
2368
|
+
...prev,
|
|
2369
|
+
isStreaming: false,
|
|
2370
|
+
showTinkering: false,
|
|
2371
|
+
streamingText: void 0,
|
|
2372
|
+
streamingToolCall: void 0
|
|
2373
|
+
}));
|
|
2374
|
+
}, [client, session.sessionId]);
|
|
2375
|
+
const clear = react.useCallback(() => {
|
|
2376
|
+
setSession({
|
|
2377
|
+
conversation: [],
|
|
2378
|
+
isStreaming: false
|
|
2379
|
+
});
|
|
2380
|
+
}, []);
|
|
2381
|
+
const setSessionId = react.useCallback((id) => {
|
|
2382
|
+
setSession((prev) => ({ ...prev, sessionId: id }));
|
|
2383
|
+
}, []);
|
|
2384
|
+
const setConversation = react.useCallback(
|
|
2385
|
+
(entries, sessionId) => {
|
|
2386
|
+
setSession((prev) => ({
|
|
2387
|
+
...prev,
|
|
2388
|
+
conversation: entries,
|
|
2389
|
+
sessionId: sessionId ?? prev.sessionId,
|
|
2390
|
+
isStreaming: false,
|
|
2391
|
+
streamingText: void 0,
|
|
2392
|
+
streamingToolCall: void 0,
|
|
2393
|
+
showTinkering: false
|
|
2394
|
+
}));
|
|
2395
|
+
},
|
|
2396
|
+
[]
|
|
2397
|
+
);
|
|
2398
|
+
const reconnect = react.useCallback(
|
|
2399
|
+
async (targetSessionId, freshConversation, streamingPrompt) => {
|
|
2400
|
+
let conversation = freshConversation ?? [];
|
|
2401
|
+
if (streamingPrompt) {
|
|
2402
|
+
conversation = [
|
|
2403
|
+
...conversation,
|
|
2404
|
+
{
|
|
2405
|
+
type: "user_text",
|
|
2406
|
+
text: streamingPrompt,
|
|
2407
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2408
|
+
}
|
|
2409
|
+
];
|
|
2410
|
+
}
|
|
2411
|
+
setSession((prev) => ({
|
|
2412
|
+
...prev,
|
|
2413
|
+
sessionId: targetSessionId,
|
|
2414
|
+
conversation,
|
|
2415
|
+
isStreaming: true,
|
|
2416
|
+
showTinkering: true
|
|
2417
|
+
}));
|
|
2418
|
+
accumulatedTextRef.current = {};
|
|
2419
|
+
const { emitter, abort } = client.reconnect(targetSessionId, -1);
|
|
2420
|
+
abortRef.current = abort;
|
|
2421
|
+
setupEventHandlers(emitter);
|
|
2422
|
+
},
|
|
2423
|
+
[client, setupEventHandlers]
|
|
2424
|
+
);
|
|
2425
|
+
const loadSession = react.useCallback(
|
|
2426
|
+
async (sessionId) => {
|
|
2427
|
+
const response = await client.getSession(sessionId);
|
|
2428
|
+
return response.conversation;
|
|
2429
|
+
},
|
|
2430
|
+
[client]
|
|
2431
|
+
);
|
|
2432
|
+
return {
|
|
2433
|
+
sessionId: session.sessionId,
|
|
2434
|
+
conversation: session.conversation,
|
|
2435
|
+
isStreaming: session.isStreaming,
|
|
2436
|
+
streamingText: session.streamingText,
|
|
2437
|
+
streamingToolCall: session.streamingToolCall,
|
|
2438
|
+
showTinkering: session.showTinkering ?? false,
|
|
2439
|
+
sendMessage,
|
|
2440
|
+
cancel,
|
|
2441
|
+
clear,
|
|
2442
|
+
reconnect,
|
|
2443
|
+
setSessionId,
|
|
2444
|
+
setConversation,
|
|
2445
|
+
loadSession
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
function detectMediaType(url) {
|
|
2449
|
+
if (/\.(mp4|mov|webm|avi|mkv|m4v)(\?|$)/i.test(url)) return "video";
|
|
2450
|
+
if (/\.(mp3|wav|m4a|aac|ogg)(\?|$)/i.test(url)) return "audio";
|
|
2451
|
+
return "image";
|
|
2452
|
+
}
|
|
2453
|
+
function extractMediaFromEntry(entry) {
|
|
2454
|
+
if (entry.type !== "tool_result") return null;
|
|
2455
|
+
let result = null;
|
|
2456
|
+
if (typeof entry.content === "string") {
|
|
2457
|
+
try {
|
|
2458
|
+
result = JSON.parse(entry.content);
|
|
2459
|
+
} catch {
|
|
2460
|
+
return null;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
if (!result) return null;
|
|
2464
|
+
const mediaInfo = result.mediaInfo || result.data?.mediaInfo;
|
|
2465
|
+
if (mediaInfo?.urls && mediaInfo.urls.length > 0) {
|
|
2466
|
+
const type = detectMediaType(mediaInfo.urls[0]);
|
|
2467
|
+
return { urls: mediaInfo.urls, type };
|
|
2468
|
+
}
|
|
2469
|
+
const cdnUrls = result.output_cdn_urls || result.data?.output_cdn_urls;
|
|
2470
|
+
if (cdnUrls && cdnUrls.length > 0) {
|
|
2471
|
+
const type = detectMediaType(cdnUrls[0]);
|
|
2472
|
+
return { urls: cdnUrls, type };
|
|
2473
|
+
}
|
|
2474
|
+
return null;
|
|
2475
|
+
}
|
|
2476
|
+
function useSkills() {
|
|
2477
|
+
const client = useAgentClient();
|
|
2478
|
+
const [isSaving, setIsSaving] = react.useState(false);
|
|
2479
|
+
const {
|
|
2480
|
+
data: skillsData,
|
|
2481
|
+
error: skillsError,
|
|
2482
|
+
mutate: mutateSkills,
|
|
2483
|
+
isLoading: isLoadingSkills
|
|
2484
|
+
} = useSWR__default.default("agent-sdk-skills", () => client.listSkills(), {
|
|
2485
|
+
revalidateOnFocus: false,
|
|
2486
|
+
dedupingInterval: 6e4
|
|
2487
|
+
// 1 minute
|
|
2488
|
+
});
|
|
2489
|
+
const {
|
|
2490
|
+
data: orgSkillsData,
|
|
2491
|
+
error: orgSkillsError,
|
|
2492
|
+
mutate: mutateOrgSkills,
|
|
2493
|
+
isLoading: isLoadingOrgSkills
|
|
2494
|
+
} = useSWR__default.default(
|
|
2495
|
+
client.hasOrgContext ? "agent-sdk-skills-org" : null,
|
|
2496
|
+
() => client.listOrgSkills(),
|
|
2497
|
+
{
|
|
2498
|
+
revalidateOnFocus: false,
|
|
2499
|
+
dedupingInterval: 6e4
|
|
2500
|
+
}
|
|
2501
|
+
);
|
|
2502
|
+
const saveSkill = react.useCallback(
|
|
2503
|
+
async (skill) => {
|
|
2504
|
+
setIsSaving(true);
|
|
2505
|
+
try {
|
|
2506
|
+
await client.saveSkill(skill);
|
|
2507
|
+
await mutateSkills();
|
|
2508
|
+
} finally {
|
|
2509
|
+
setIsSaving(false);
|
|
2510
|
+
}
|
|
2511
|
+
},
|
|
2512
|
+
[client, mutateSkills]
|
|
2513
|
+
);
|
|
2514
|
+
const deleteSkill = react.useCallback(
|
|
2515
|
+
async (name) => {
|
|
2516
|
+
await client.deleteSkill(name);
|
|
2517
|
+
await mutateSkills();
|
|
2518
|
+
},
|
|
2519
|
+
[client, mutateSkills]
|
|
2520
|
+
);
|
|
2521
|
+
const refresh = react.useCallback(async () => {
|
|
2522
|
+
await mutateSkills();
|
|
2523
|
+
}, [mutateSkills]);
|
|
2524
|
+
const saveOrgSkill = react.useCallback(
|
|
2525
|
+
async (skill) => {
|
|
2526
|
+
setIsSaving(true);
|
|
2527
|
+
try {
|
|
2528
|
+
await client.saveOrgSkill(skill);
|
|
2529
|
+
await mutateOrgSkills();
|
|
2530
|
+
} finally {
|
|
2531
|
+
setIsSaving(false);
|
|
2532
|
+
}
|
|
2533
|
+
},
|
|
2534
|
+
[client, mutateOrgSkills]
|
|
2535
|
+
);
|
|
2536
|
+
const deleteOrgSkill = react.useCallback(
|
|
2537
|
+
async (name) => {
|
|
2538
|
+
await client.deleteOrgSkill(name);
|
|
2539
|
+
await mutateOrgSkills();
|
|
2540
|
+
},
|
|
2541
|
+
[client, mutateOrgSkills]
|
|
2542
|
+
);
|
|
2543
|
+
const refreshOrg = react.useCallback(async () => {
|
|
2544
|
+
await mutateOrgSkills();
|
|
2545
|
+
}, [mutateOrgSkills]);
|
|
2546
|
+
return {
|
|
2547
|
+
// Personal skills
|
|
2548
|
+
skills: skillsData?.skills ?? [],
|
|
2549
|
+
isLoading: isLoadingSkills || isSaving,
|
|
2550
|
+
error: skillsError,
|
|
2551
|
+
refresh,
|
|
2552
|
+
saveSkill,
|
|
2553
|
+
deleteSkill,
|
|
2554
|
+
// Org skills
|
|
2555
|
+
orgSkills: orgSkillsData?.skills ?? [],
|
|
2556
|
+
isLoadingOrg: isLoadingOrgSkills,
|
|
2557
|
+
orgError: orgSkillsError,
|
|
2558
|
+
refreshOrg,
|
|
2559
|
+
saveOrgSkill,
|
|
2560
|
+
deleteOrgSkill
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
function useJadeSession(options = {}) {
|
|
2564
|
+
const {
|
|
2565
|
+
skipSkillContext = true,
|
|
2566
|
+
processingOptions,
|
|
2567
|
+
...agentOptions
|
|
2568
|
+
} = options;
|
|
2569
|
+
const session = useAgentSession(agentOptions);
|
|
2570
|
+
const processedConversation = react.useMemo(
|
|
2571
|
+
() => processConversation(session.conversation, {
|
|
2572
|
+
skipSkillContext,
|
|
2573
|
+
pairToolResults: true,
|
|
2574
|
+
extractMedia: true,
|
|
2575
|
+
...processingOptions
|
|
2576
|
+
}),
|
|
2577
|
+
[session.conversation, skipSkillContext, processingOptions]
|
|
2578
|
+
);
|
|
2579
|
+
const media = react.useMemo(
|
|
2580
|
+
() => extractMedia(session.conversation),
|
|
2581
|
+
[session.conversation]
|
|
2582
|
+
);
|
|
2583
|
+
return {
|
|
2584
|
+
...session,
|
|
2585
|
+
processedConversation,
|
|
2586
|
+
media
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
function useMedia(conversation) {
|
|
2590
|
+
return react.useMemo(() => extractMedia(conversation), [conversation]);
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// src/jade/react/JadeProvider.tsx
|
|
2594
|
+
var JadeProvider = AgentProvider;
|
|
1676
2595
|
|
|
1677
2596
|
exports.AgentClient = AgentClient;
|
|
1678
2597
|
exports.AgentClientError = AgentClientError;
|
|
1679
2598
|
exports.AgentEventEmitter = AgentEventEmitter;
|
|
2599
|
+
exports.AgentProvider = AgentProvider;
|
|
1680
2600
|
exports.AuthenticationError = AuthenticationError;
|
|
2601
|
+
exports.JadeClient = AgentClient;
|
|
2602
|
+
exports.JadeProvider = JadeProvider;
|
|
1681
2603
|
exports.RequestError = RequestError;
|
|
1682
2604
|
exports.SessionNotFoundError = SessionNotFoundError;
|
|
1683
2605
|
exports.SkillNotFoundError = SkillNotFoundError;
|
|
@@ -1694,3 +2616,8 @@ exports.hasSuggestions = hasSuggestions;
|
|
|
1694
2616
|
exports.parseSuggestions = parseSuggestions;
|
|
1695
2617
|
exports.parseToolResultContent = parseToolResultContent;
|
|
1696
2618
|
exports.processConversation = processConversation;
|
|
2619
|
+
exports.useAgentClient = useAgentClient;
|
|
2620
|
+
exports.useAgentSession = useAgentSession;
|
|
2621
|
+
exports.useJadeSession = useJadeSession;
|
|
2622
|
+
exports.useMedia = useMedia;
|
|
2623
|
+
exports.useSkills = useSkills;
|