@rozenite/storage-plugin 1.4.0 → 1.5.1
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/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/useStorageAgentTools.d.ts +2 -0
- package/dist/useRozeniteStoragePlugin.cjs +1 -1
- package/dist/useRozeniteStoragePlugin.js +344 -113
- package/package.json +5 -4
- package/src/react-native/useRozeniteStoragePlugin.ts +3 -0
- package/src/react-native/useStorageAgentTools.ts +332 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @rozenite/storage-plugin
|
|
2
2
|
|
|
3
|
+
## 1.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies []:
|
|
8
|
+
- @rozenite/agent-bridge@1.5.1
|
|
9
|
+
- @rozenite/plugin-bridge@1.5.1
|
|
10
|
+
|
|
11
|
+
## 1.5.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- [#190](https://github.com/callstackincubator/rozenite/pull/190) [`5ae53a4`](https://github.com/callstackincubator/rozenite/commit/5ae53a4b509adbd8536ea24812f7ca523a95b625) Thanks [@V3RON](https://github.com/V3RON)! - Added Rozenite for Agents support to the Controls, MMKV, React Navigation, and Storage plugins.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [[`5ae53a4`](https://github.com/callstackincubator/rozenite/commit/5ae53a4b509adbd8536ea24812f7ca523a95b625)]:
|
|
20
|
+
- @rozenite/agent-bridge@1.5.0
|
|
21
|
+
- @rozenite/plugin-bridge@1.5.0
|
|
22
|
+
|
|
3
23
|
## 1.4.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -72,6 +72,6 @@ createAsyncStorageAdapter({
|
|
|
72
72
|
## Notes
|
|
73
73
|
|
|
74
74
|
- Unsupported value types are disabled in UI create/edit flows.
|
|
75
|
-
- Type support is enforced in UI, runtime and
|
|
75
|
+
- Type support is enforced in UI, runtime and Agent tools.
|
|
76
76
|
- Storages without subscriptions automatically use internal polling updates.
|
|
77
77
|
- Per-storage blacklists are supported through adapter configuration.
|
package/dist/rozenite.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@rozenite/storage-plugin","version":"1.
|
|
1
|
+
{"name":"@rozenite/storage-plugin","version":"1.5.0","description":"Generic Storage Inspector for Rozenite.","panels":[{"name":"Storage","source":"/panel.html"}]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const v=require("@rozenite/plugin-bridge"),m=require("react"),$=require("./types.cjs"),p=require("@rozenite/agent-bridge"),P=1500,f=e=>e.kind==="async",I=e=>e.type==="buffer"?`${e.type}:${e.value.join(",")}`:`${e.type}:${String(e.value)}`,T=e=>new Map(e.map(t=>[t.key,t])),h=(e,t)=>e.blacklist?(e.blacklist.lastIndex=0,e.blacklist.test(t)):!1,R=(e,t,r)=>{if(!$.supportsType(e,t))throw new Error(`Type "${t}" is not supported by storage "${r.storageId}" in adapter "${r.adapterId}".`)},w=async e=>(f(e),e.getAllKeys()),b=async(e,t)=>(f(e),e.get(t)),A=async(e,t)=>{if(f(e)){await e.set(t);return}e.set(t)},z=async(e,t)=>{if(f(e)){await e.delete(t);return}e.delete(t)},S=async e=>{const t=await e();return T(t)},M=(e,t,r)=>{t.forEach((s,n)=>{const o=e.get(n);if(!o){r.onSet(s);return}I(o)!==I(s)&&r.onSet(s)}),e.forEach((s,n)=>{t.has(n)||r.onDelete(n)})},k=async(e,t)=>{let r=await S(e);const s=setInterval(async()=>{try{const n=await S(e);M(r,n,t),r=n}catch{}},P);return{remove:()=>{clearInterval(s)}}},x=(e,t)=>{const r=t.storage,s={adapterId:e.id,storageId:t.id},n=async d=>{if(!h(t,d))return b(r,d)},o=async()=>{const d=await w(r);return(await Promise.all(d.filter(c=>!h(t,c)).map(c=>b(r,c)))).filter(c=>!!c)};return{id:$.getStorageViewId(s),target:s,adapterName:e.name,storageName:t.name,capabilities:t.capabilities,get:n,set:async d=>{R(t.capabilities,d.type,s),await A(r,d)},delete:async d=>{await z(r,d)},getAllKeys:async()=>(await w(r)).filter(a=>!h(t,a)),getAllEntries:o,watch:async({onSet:d,onDelete:a})=>r.subscribe?r.subscribe(async c=>{try{const i=await n(c);if(!i){a(c);return}d(i)}catch{}}):k(o,{onSet:d,onDelete:a})}},j=e=>e.flatMap(t=>t.storages.map(r=>x(t,r))),g="@rozenite/storage-plugin",y={adapterId:{type:"string",description:"Storage adapter ID. Required when multiple adapters are configured."},storageId:{type:"string",description:"Storage node ID within the adapter. Required when multiple storages are configured."}},q={name:"list-storages",description:"List all storage adapters and their storage nodes currently available on the device, including supported entry types and entry counts.",inputSchema:{type:"object",properties:{}}},K={name:"list-entries",description:"List keys in a storage. This call intentionally does not return entry values to keep responses token-efficient.",inputSchema:{type:"object",properties:{...y,offset:{type:"number",description:"Pagination offset. Defaults to 0."},limit:{type:"number",description:"Pagination size. Defaults to 100."}}}},N={name:"read-entry",description:"Read a single storage entry value by key.",inputSchema:{type:"object",properties:{...y,key:{type:"string",description:"Entry key."}},required:["key"]}},V={name:"create-entry",description:"Create a new storage entry. Fails if the key already exists.",inputSchema:{type:"object",properties:{...y,key:{type:"string",description:"Entry key."},type:{type:"string",enum:["string","number","boolean","buffer"],description:"Entry value type. Must be one of the supported types for the target storage (see list-storages)."},value:{description:"Entry value matching the provided type."}},required:["key","type","value"]}},F={name:"edit-entry",description:"Edit an existing storage entry. Fails if the key does not exist.",inputSchema:{type:"object",properties:{...y,key:{type:"string",description:"Entry key."},type:{type:"string",enum:["string","number","boolean","buffer"],description:"Entry value type. Must be one of the supported types for the target storage (see list-storages)."},value:{description:"Entry value matching the provided type."}},required:["key","type","value"]}},D={name:"remove-entry",description:"Remove a storage entry by key.",inputSchema:{type:"object",properties:{...y,key:{type:"string",description:"Entry key."}},required:["key"]}},E=(e,t)=>{if(e==="string"){if(typeof t!="string")throw new Error("Expected string value for type=string");return t}if(e==="number"){if(typeof t!="number"||Number.isNaN(t))throw new Error("Expected number value for type=number");return t}if(e==="boolean"){if(typeof t!="boolean")throw new Error("Expected boolean value for type=boolean");return t}if(!Array.isArray(t)||!t.every(r=>Number.isInteger(r)))throw new Error("Expected number[] value for type=buffer");return t},u=e=>`${e.target.adapterId}/${e.target.storageId}`,L=e=>{const t=m.useCallback((r,s)=>{if(e.length===0)throw new Error("No storages are registered.");if(r!==void 0||s!==void 0){const n=e.find(o=>(r===void 0||o.target.adapterId===r)&&(s===void 0||o.target.storageId===s));if(!n){const o=[r&&`adapterId="${r}"`,s&&`storageId="${s}"`].filter(Boolean).join(", ");throw new Error(`No storage matched ${o}. Available: ${e.map(u).join(", ")}`)}return n}if(e.length>1)throw new Error(`Multiple storages detected. Provide adapterId and/or storageId. Available: ${e.map(u).join(", ")}`);return e[0]},[e]);p.useRozenitePluginAgentTool({pluginId:g,tool:q,handler:async()=>({storages:await Promise.all(e.map(async r=>({adapterId:r.target.adapterId,storageId:r.target.storageId,adapterName:r.adapterName,storageName:r.storageName,supportedTypes:r.capabilities.supportedTypes,entryCount:(await r.getAllKeys()).length})))})}),p.useRozenitePluginAgentTool({pluginId:g,tool:K,handler:async({adapterId:r,storageId:s,offset:n=0,limit:o=100})=>{const d=t(r,s),a=await d.getAllKeys(),c=Math.max(0,Math.floor(n)),i=Math.max(1,Math.floor(o)),l=a.slice(c,c+i);return{adapterId:d.target.adapterId,storageId:d.target.storageId,total:a.length,offset:c,limit:i,keys:l}}}),p.useRozenitePluginAgentTool({pluginId:g,tool:N,handler:async({adapterId:r,storageId:s,key:n})=>{const o=t(r,s),d=await o.get(n);if(!d)throw new Error(`Key "${n}" not found in storage "${u(o)}".`);return{adapterId:o.target.adapterId,storageId:o.target.storageId,entry:d}}}),p.useRozenitePluginAgentTool({pluginId:g,tool:V,handler:async({adapterId:r,storageId:s,key:n,type:o,value:d})=>{const a=t(r,s);if(await a.get(n))throw new Error(`Key "${n}" already exists in storage "${u(a)}". Use edit-entry instead.`);const i=E(o,d);return await a.set({key:n,type:o,value:i}),{adapterId:a.target.adapterId,storageId:a.target.storageId,created:!0,key:n}}}),p.useRozenitePluginAgentTool({pluginId:g,tool:F,handler:async({adapterId:r,storageId:s,key:n,type:o,value:d})=>{const a=t(r,s);if(!await a.get(n))throw new Error(`Key "${n}" not found in storage "${u(a)}". Use create-entry instead.`);const i=E(o,d);return await a.set({key:n,type:o,value:i}),{adapterId:a.target.adapterId,storageId:a.target.storageId,edited:!0,key:n}}}),p.useRozenitePluginAgentTool({pluginId:g,tool:D,handler:async({adapterId:r,storageId:s,key:n})=>{const o=t(r,s),d=!!await o.get(n);return await o.delete(n),{adapterId:o.target.adapterId,storageId:o.target.storageId,removed:d,key:n}}})},C=({storages:e})=>{const t=m.useMemo(()=>j(e),[e]);L(t);const r=v.useRozeniteDevToolsClient({pluginId:"@rozenite/storage-plugin"});return m.useEffect(()=>{if(!r)return;const s=async a=>{const c=a?t.filter(i=>i.id===a):t;for(const i of c)try{const l=await i.getAllEntries();r.send("snapshot",{type:"snapshot",target:i.target,adapterName:i.adapterName,storageName:i.storageName,capabilities:i.capabilities,entries:l})}catch(l){console.warn(`[Rozenite] Storage Plugin: Failed to snapshot ${i.target.adapterId}/${i.target.storageId}.`,l)}};s();const n=[];let o=!1;Promise.all(t.map(async a=>{try{const c=await a.watch({onSet:i=>{r.send("set-entry",{type:"set-entry",target:a.target,entry:i})},onDelete:i=>{r.send("delete-entry",{type:"delete-entry",target:a.target,key:i})}});if(o){c.remove();return}n.push(c)}catch(c){console.warn(`[Rozenite] Storage Plugin: Failed to attach watcher for ${a.target.adapterId}/${a.target.storageId}.`,c)}}));const d=[r.onMessage("set-entry",async({target:a,entry:c})=>{const i=t.find(l=>l.target.adapterId===a.adapterId&&l.target.storageId===a.storageId);if(!i){console.warn(`[Rozenite] Storage Plugin: Storage target not found for ${a.adapterId}/${a.storageId}`);return}try{await i.set(c)}catch(l){console.warn(`[Rozenite] Storage Plugin: Failed to set entry in ${a.adapterId}/${a.storageId}.`,l)}}),r.onMessage("delete-entry",async({target:a,key:c})=>{const i=t.find(l=>l.target.adapterId===a.adapterId&&l.target.storageId===a.storageId);if(!i){console.warn(`[Rozenite] Storage Plugin: Storage target not found for ${a.adapterId}/${a.storageId}`);return}try{await i.delete(c)}catch(l){console.warn(`[Rozenite] Storage Plugin: Failed to delete entry in ${a.adapterId}/${a.storageId}.`,l)}}),r.onMessage("get-snapshot",async({target:a})=>{if(a==="all"){await s();return}await s(`${a.adapterId}:${a.storageId}`)})];return()=>{o=!0,n.forEach(a=>a.remove()),d.forEach(a=>a.remove())}},[r,t]),r};exports.useRozeniteStoragePlugin=C;
|
|
@@ -1,212 +1,443 @@
|
|
|
1
|
-
import { useRozeniteDevToolsClient as
|
|
2
|
-
import { useMemo as
|
|
3
|
-
import { g as
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { useRozeniteDevToolsClient as S } from "@rozenite/plugin-bridge";
|
|
2
|
+
import { useCallback as $, useMemo as v, useEffect as P } from "react";
|
|
3
|
+
import { g as T, s as k } from "./types.js";
|
|
4
|
+
import { useRozenitePluginAgentTool as l } from "@rozenite/agent-bridge";
|
|
5
|
+
const x = 1500, f = (e) => e.kind === "async", m = (e) => e.type === "buffer" ? `${e.type}:${e.value.join(",")}` : `${e.type}:${String(e.value)}`, M = (e) => new Map(e.map((t) => [t.key, t])), h = (e, t) => e.blacklist ? (e.blacklist.lastIndex = 0, e.blacklist.test(t)) : !1, A = (e, t, r) => {
|
|
6
|
+
if (!k(e, t))
|
|
6
7
|
throw new Error(
|
|
7
|
-
`Type "${
|
|
8
|
+
`Type "${t}" is not supported by storage "${r.storageId}" in adapter "${r.adapterId}".`
|
|
8
9
|
);
|
|
9
|
-
},
|
|
10
|
-
if (
|
|
11
|
-
await
|
|
10
|
+
}, I = async (e) => (f(e), e.getAllKeys()), w = async (e, t) => (f(e), e.get(t)), R = async (e, t) => {
|
|
11
|
+
if (f(e)) {
|
|
12
|
+
await e.set(t);
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
}, z = async (
|
|
16
|
-
if (
|
|
17
|
-
await
|
|
15
|
+
e.set(t);
|
|
16
|
+
}, z = async (e, t) => {
|
|
17
|
+
if (f(e)) {
|
|
18
|
+
await e.delete(t);
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
-
const
|
|
23
|
-
return
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
if (!
|
|
28
|
-
r.onSet(
|
|
21
|
+
e.delete(t);
|
|
22
|
+
}, b = async (e) => {
|
|
23
|
+
const t = await e();
|
|
24
|
+
return M(t);
|
|
25
|
+
}, K = (e, t, r) => {
|
|
26
|
+
t.forEach((s, n) => {
|
|
27
|
+
const o = e.get(n);
|
|
28
|
+
if (!o) {
|
|
29
|
+
r.onSet(s);
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
}),
|
|
33
|
-
|
|
32
|
+
m(o) !== m(s) && r.onSet(s);
|
|
33
|
+
}), e.forEach((s, n) => {
|
|
34
|
+
t.has(n) || r.onDelete(n);
|
|
34
35
|
});
|
|
35
|
-
},
|
|
36
|
-
let r = await
|
|
37
|
-
const
|
|
36
|
+
}, N = async (e, t) => {
|
|
37
|
+
let r = await b(e);
|
|
38
|
+
const s = setInterval(async () => {
|
|
38
39
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
40
|
+
const n = await b(e);
|
|
41
|
+
K(r, n, t), r = n;
|
|
41
42
|
} catch {
|
|
42
43
|
}
|
|
43
|
-
},
|
|
44
|
+
}, x);
|
|
44
45
|
return {
|
|
45
46
|
remove: () => {
|
|
46
|
-
clearInterval(
|
|
47
|
+
clearInterval(s);
|
|
47
48
|
}
|
|
48
49
|
};
|
|
49
|
-
},
|
|
50
|
-
const r =
|
|
51
|
-
adapterId:
|
|
52
|
-
storageId:
|
|
53
|
-
},
|
|
54
|
-
if (!
|
|
55
|
-
return
|
|
56
|
-
},
|
|
57
|
-
const
|
|
50
|
+
}, j = (e, t) => {
|
|
51
|
+
const r = t.storage, s = {
|
|
52
|
+
adapterId: e.id,
|
|
53
|
+
storageId: t.id
|
|
54
|
+
}, n = async (d) => {
|
|
55
|
+
if (!h(t, d))
|
|
56
|
+
return w(r, d);
|
|
57
|
+
}, o = async () => {
|
|
58
|
+
const d = await I(r);
|
|
58
59
|
return (await Promise.all(
|
|
59
|
-
|
|
60
|
-
)).filter((
|
|
60
|
+
d.filter((c) => !h(t, c)).map((c) => w(r, c))
|
|
61
|
+
)).filter((c) => !!c);
|
|
61
62
|
};
|
|
62
63
|
return {
|
|
63
|
-
id:
|
|
64
|
-
target:
|
|
65
|
-
adapterName:
|
|
66
|
-
storageName:
|
|
67
|
-
capabilities:
|
|
68
|
-
get:
|
|
69
|
-
set: async (
|
|
70
|
-
|
|
64
|
+
id: T(s),
|
|
65
|
+
target: s,
|
|
66
|
+
adapterName: e.name,
|
|
67
|
+
storageName: t.name,
|
|
68
|
+
capabilities: t.capabilities,
|
|
69
|
+
get: n,
|
|
70
|
+
set: async (d) => {
|
|
71
|
+
A(t.capabilities, d.type, s), await R(r, d);
|
|
71
72
|
},
|
|
72
|
-
delete: async (
|
|
73
|
-
await z(r,
|
|
73
|
+
delete: async (d) => {
|
|
74
|
+
await z(r, d);
|
|
74
75
|
},
|
|
75
|
-
getAllKeys: async () => (await
|
|
76
|
-
getAllEntries:
|
|
77
|
-
watch: async ({ onSet:
|
|
76
|
+
getAllKeys: async () => (await I(r)).filter((a) => !h(t, a)),
|
|
77
|
+
getAllEntries: o,
|
|
78
|
+
watch: async ({ onSet: d, onDelete: a }) => r.subscribe ? r.subscribe(async (c) => {
|
|
78
79
|
try {
|
|
79
|
-
const
|
|
80
|
-
if (!
|
|
81
|
-
a(
|
|
80
|
+
const i = await n(c);
|
|
81
|
+
if (!i) {
|
|
82
|
+
a(c);
|
|
82
83
|
return;
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
+
d(i);
|
|
85
86
|
} catch {
|
|
86
87
|
}
|
|
87
|
-
}) :
|
|
88
|
+
}) : N(o, { onSet: d, onDelete: a })
|
|
88
89
|
};
|
|
89
|
-
},
|
|
90
|
-
(
|
|
91
|
-
),
|
|
92
|
-
|
|
90
|
+
}, V = (e) => e.flatMap(
|
|
91
|
+
(t) => t.storages.map((r) => j(t, r))
|
|
92
|
+
), g = "@rozenite/storage-plugin", y = {
|
|
93
|
+
adapterId: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "Storage adapter ID. Required when multiple adapters are configured."
|
|
96
|
+
},
|
|
97
|
+
storageId: {
|
|
98
|
+
type: "string",
|
|
99
|
+
description: "Storage node ID within the adapter. Required when multiple storages are configured."
|
|
100
|
+
}
|
|
101
|
+
}, F = {
|
|
102
|
+
name: "list-storages",
|
|
103
|
+
description: "List all storage adapters and their storage nodes currently available on the device, including supported entry types and entry counts.",
|
|
104
|
+
inputSchema: { type: "object", properties: {} }
|
|
105
|
+
}, q = {
|
|
106
|
+
name: "list-entries",
|
|
107
|
+
description: "List keys in a storage. This call intentionally does not return entry values to keep responses token-efficient.",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
...y,
|
|
112
|
+
offset: {
|
|
113
|
+
type: "number",
|
|
114
|
+
description: "Pagination offset. Defaults to 0."
|
|
115
|
+
},
|
|
116
|
+
limit: {
|
|
117
|
+
type: "number",
|
|
118
|
+
description: "Pagination size. Defaults to 100."
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}, D = {
|
|
123
|
+
name: "read-entry",
|
|
124
|
+
description: "Read a single storage entry value by key.",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
...y,
|
|
129
|
+
key: { type: "string", description: "Entry key." }
|
|
130
|
+
},
|
|
131
|
+
required: ["key"]
|
|
132
|
+
}
|
|
133
|
+
}, L = {
|
|
134
|
+
name: "create-entry",
|
|
135
|
+
description: "Create a new storage entry. Fails if the key already exists.",
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {
|
|
139
|
+
...y,
|
|
140
|
+
key: { type: "string", description: "Entry key." },
|
|
141
|
+
type: {
|
|
142
|
+
type: "string",
|
|
143
|
+
enum: ["string", "number", "boolean", "buffer"],
|
|
144
|
+
description: "Entry value type. Must be one of the supported types for the target storage (see list-storages)."
|
|
145
|
+
},
|
|
146
|
+
value: {
|
|
147
|
+
description: "Entry value matching the provided type."
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
required: ["key", "type", "value"]
|
|
151
|
+
}
|
|
152
|
+
}, C = {
|
|
153
|
+
name: "edit-entry",
|
|
154
|
+
description: "Edit an existing storage entry. Fails if the key does not exist.",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
...y,
|
|
159
|
+
key: { type: "string", description: "Entry key." },
|
|
160
|
+
type: {
|
|
161
|
+
type: "string",
|
|
162
|
+
enum: ["string", "number", "boolean", "buffer"],
|
|
163
|
+
description: "Entry value type. Must be one of the supported types for the target storage (see list-storages)."
|
|
164
|
+
},
|
|
165
|
+
value: {
|
|
166
|
+
description: "Entry value matching the provided type."
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
required: ["key", "type", "value"]
|
|
170
|
+
}
|
|
171
|
+
}, _ = {
|
|
172
|
+
name: "remove-entry",
|
|
173
|
+
description: "Remove a storage entry by key.",
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: "object",
|
|
176
|
+
properties: {
|
|
177
|
+
...y,
|
|
178
|
+
key: { type: "string", description: "Entry key." }
|
|
179
|
+
},
|
|
180
|
+
required: ["key"]
|
|
181
|
+
}
|
|
182
|
+
}, E = (e, t) => {
|
|
183
|
+
if (e === "string") {
|
|
184
|
+
if (typeof t != "string")
|
|
185
|
+
throw new Error("Expected string value for type=string");
|
|
186
|
+
return t;
|
|
187
|
+
}
|
|
188
|
+
if (e === "number") {
|
|
189
|
+
if (typeof t != "number" || Number.isNaN(t))
|
|
190
|
+
throw new Error("Expected number value for type=number");
|
|
191
|
+
return t;
|
|
192
|
+
}
|
|
193
|
+
if (e === "boolean") {
|
|
194
|
+
if (typeof t != "boolean")
|
|
195
|
+
throw new Error("Expected boolean value for type=boolean");
|
|
196
|
+
return t;
|
|
197
|
+
}
|
|
198
|
+
if (!Array.isArray(t) || !t.every((r) => Number.isInteger(r)))
|
|
199
|
+
throw new Error("Expected number[] value for type=buffer");
|
|
200
|
+
return t;
|
|
201
|
+
}, u = (e) => `${e.target.adapterId}/${e.target.storageId}`, O = (e) => {
|
|
202
|
+
const t = $(
|
|
203
|
+
(r, s) => {
|
|
204
|
+
if (e.length === 0)
|
|
205
|
+
throw new Error("No storages are registered.");
|
|
206
|
+
if (r !== void 0 || s !== void 0) {
|
|
207
|
+
const n = e.find(
|
|
208
|
+
(o) => (r === void 0 || o.target.adapterId === r) && (s === void 0 || o.target.storageId === s)
|
|
209
|
+
);
|
|
210
|
+
if (!n) {
|
|
211
|
+
const o = [
|
|
212
|
+
r && `adapterId="${r}"`,
|
|
213
|
+
s && `storageId="${s}"`
|
|
214
|
+
].filter(Boolean).join(", ");
|
|
215
|
+
throw new Error(
|
|
216
|
+
`No storage matched ${o}. Available: ${e.map(u).join(", ")}`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return n;
|
|
220
|
+
}
|
|
221
|
+
if (e.length > 1)
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Multiple storages detected. Provide adapterId and/or storageId. Available: ${e.map(u).join(", ")}`
|
|
224
|
+
);
|
|
225
|
+
return e[0];
|
|
226
|
+
},
|
|
227
|
+
[e]
|
|
228
|
+
);
|
|
229
|
+
l({
|
|
230
|
+
pluginId: g,
|
|
231
|
+
tool: F,
|
|
232
|
+
handler: async () => ({
|
|
233
|
+
storages: await Promise.all(
|
|
234
|
+
e.map(async (r) => ({
|
|
235
|
+
adapterId: r.target.adapterId,
|
|
236
|
+
storageId: r.target.storageId,
|
|
237
|
+
adapterName: r.adapterName,
|
|
238
|
+
storageName: r.storageName,
|
|
239
|
+
supportedTypes: r.capabilities.supportedTypes,
|
|
240
|
+
entryCount: (await r.getAllKeys()).length
|
|
241
|
+
}))
|
|
242
|
+
)
|
|
243
|
+
})
|
|
244
|
+
}), l({
|
|
245
|
+
pluginId: g,
|
|
246
|
+
tool: q,
|
|
247
|
+
handler: async ({ adapterId: r, storageId: s, offset: n = 0, limit: o = 100 }) => {
|
|
248
|
+
const d = t(r, s), a = await d.getAllKeys(), c = Math.max(0, Math.floor(n)), i = Math.max(1, Math.floor(o)), p = a.slice(c, c + i);
|
|
249
|
+
return {
|
|
250
|
+
adapterId: d.target.adapterId,
|
|
251
|
+
storageId: d.target.storageId,
|
|
252
|
+
total: a.length,
|
|
253
|
+
offset: c,
|
|
254
|
+
limit: i,
|
|
255
|
+
keys: p
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}), l({
|
|
259
|
+
pluginId: g,
|
|
260
|
+
tool: D,
|
|
261
|
+
handler: async ({ adapterId: r, storageId: s, key: n }) => {
|
|
262
|
+
const o = t(r, s), d = await o.get(n);
|
|
263
|
+
if (!d)
|
|
264
|
+
throw new Error(
|
|
265
|
+
`Key "${n}" not found in storage "${u(o)}".`
|
|
266
|
+
);
|
|
267
|
+
return {
|
|
268
|
+
adapterId: o.target.adapterId,
|
|
269
|
+
storageId: o.target.storageId,
|
|
270
|
+
entry: d
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}), l({
|
|
274
|
+
pluginId: g,
|
|
275
|
+
tool: L,
|
|
276
|
+
handler: async ({ adapterId: r, storageId: s, key: n, type: o, value: d }) => {
|
|
277
|
+
const a = t(r, s);
|
|
278
|
+
if (await a.get(n))
|
|
279
|
+
throw new Error(
|
|
280
|
+
`Key "${n}" already exists in storage "${u(a)}". Use edit-entry instead.`
|
|
281
|
+
);
|
|
282
|
+
const i = E(o, d);
|
|
283
|
+
return await a.set({ key: n, type: o, value: i }), {
|
|
284
|
+
adapterId: a.target.adapterId,
|
|
285
|
+
storageId: a.target.storageId,
|
|
286
|
+
created: !0,
|
|
287
|
+
key: n
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}), l({
|
|
291
|
+
pluginId: g,
|
|
292
|
+
tool: C,
|
|
293
|
+
handler: async ({ adapterId: r, storageId: s, key: n, type: o, value: d }) => {
|
|
294
|
+
const a = t(r, s);
|
|
295
|
+
if (!await a.get(n))
|
|
296
|
+
throw new Error(
|
|
297
|
+
`Key "${n}" not found in storage "${u(a)}". Use create-entry instead.`
|
|
298
|
+
);
|
|
299
|
+
const i = E(o, d);
|
|
300
|
+
return await a.set({ key: n, type: o, value: i }), {
|
|
301
|
+
adapterId: a.target.adapterId,
|
|
302
|
+
storageId: a.target.storageId,
|
|
303
|
+
edited: !0,
|
|
304
|
+
key: n
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}), l({
|
|
308
|
+
pluginId: g,
|
|
309
|
+
tool: _,
|
|
310
|
+
handler: async ({ adapterId: r, storageId: s, key: n }) => {
|
|
311
|
+
const o = t(r, s), d = !!await o.get(n);
|
|
312
|
+
return await o.delete(n), {
|
|
313
|
+
adapterId: o.target.adapterId,
|
|
314
|
+
storageId: o.target.storageId,
|
|
315
|
+
removed: d,
|
|
316
|
+
key: n
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}, J = ({
|
|
321
|
+
storages: e
|
|
93
322
|
}) => {
|
|
94
|
-
const
|
|
323
|
+
const t = v(() => V(e), [e]);
|
|
324
|
+
O(t);
|
|
325
|
+
const r = S({
|
|
95
326
|
pluginId: "@rozenite/storage-plugin"
|
|
96
327
|
});
|
|
97
|
-
return
|
|
328
|
+
return P(() => {
|
|
98
329
|
if (!r)
|
|
99
330
|
return;
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
for (const
|
|
331
|
+
const s = async (a) => {
|
|
332
|
+
const c = a ? t.filter((i) => i.id === a) : t;
|
|
333
|
+
for (const i of c)
|
|
103
334
|
try {
|
|
104
|
-
const
|
|
335
|
+
const p = await i.getAllEntries();
|
|
105
336
|
r.send("snapshot", {
|
|
106
337
|
type: "snapshot",
|
|
107
|
-
target:
|
|
108
|
-
adapterName:
|
|
109
|
-
storageName:
|
|
110
|
-
capabilities:
|
|
111
|
-
entries:
|
|
338
|
+
target: i.target,
|
|
339
|
+
adapterName: i.adapterName,
|
|
340
|
+
storageName: i.storageName,
|
|
341
|
+
capabilities: i.capabilities,
|
|
342
|
+
entries: p
|
|
112
343
|
});
|
|
113
|
-
} catch (
|
|
344
|
+
} catch (p) {
|
|
114
345
|
console.warn(
|
|
115
|
-
`[Rozenite] Storage Plugin: Failed to snapshot ${
|
|
116
|
-
|
|
346
|
+
`[Rozenite] Storage Plugin: Failed to snapshot ${i.target.adapterId}/${i.target.storageId}.`,
|
|
347
|
+
p
|
|
117
348
|
);
|
|
118
349
|
}
|
|
119
350
|
};
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
let
|
|
351
|
+
s();
|
|
352
|
+
const n = [];
|
|
353
|
+
let o = !1;
|
|
123
354
|
Promise.all(
|
|
124
|
-
|
|
355
|
+
t.map(async (a) => {
|
|
125
356
|
try {
|
|
126
|
-
const
|
|
127
|
-
onSet: (
|
|
357
|
+
const c = await a.watch({
|
|
358
|
+
onSet: (i) => {
|
|
128
359
|
r.send("set-entry", {
|
|
129
360
|
type: "set-entry",
|
|
130
361
|
target: a.target,
|
|
131
|
-
entry:
|
|
362
|
+
entry: i
|
|
132
363
|
});
|
|
133
364
|
},
|
|
134
|
-
onDelete: (
|
|
365
|
+
onDelete: (i) => {
|
|
135
366
|
r.send("delete-entry", {
|
|
136
367
|
type: "delete-entry",
|
|
137
368
|
target: a.target,
|
|
138
|
-
key:
|
|
369
|
+
key: i
|
|
139
370
|
});
|
|
140
371
|
}
|
|
141
372
|
});
|
|
142
|
-
if (
|
|
143
|
-
|
|
373
|
+
if (o) {
|
|
374
|
+
c.remove();
|
|
144
375
|
return;
|
|
145
376
|
}
|
|
146
|
-
|
|
147
|
-
} catch (
|
|
377
|
+
n.push(c);
|
|
378
|
+
} catch (c) {
|
|
148
379
|
console.warn(
|
|
149
380
|
`[Rozenite] Storage Plugin: Failed to attach watcher for ${a.target.adapterId}/${a.target.storageId}.`,
|
|
150
|
-
|
|
381
|
+
c
|
|
151
382
|
);
|
|
152
383
|
}
|
|
153
384
|
})
|
|
154
385
|
);
|
|
155
|
-
const
|
|
156
|
-
r.onMessage("set-entry", async ({ target: a, entry:
|
|
157
|
-
const
|
|
158
|
-
(
|
|
386
|
+
const d = [
|
|
387
|
+
r.onMessage("set-entry", async ({ target: a, entry: c }) => {
|
|
388
|
+
const i = t.find(
|
|
389
|
+
(p) => p.target.adapterId === a.adapterId && p.target.storageId === a.storageId
|
|
159
390
|
);
|
|
160
|
-
if (!
|
|
391
|
+
if (!i) {
|
|
161
392
|
console.warn(
|
|
162
393
|
`[Rozenite] Storage Plugin: Storage target not found for ${a.adapterId}/${a.storageId}`
|
|
163
394
|
);
|
|
164
395
|
return;
|
|
165
396
|
}
|
|
166
397
|
try {
|
|
167
|
-
await
|
|
168
|
-
} catch (
|
|
398
|
+
await i.set(c);
|
|
399
|
+
} catch (p) {
|
|
169
400
|
console.warn(
|
|
170
401
|
`[Rozenite] Storage Plugin: Failed to set entry in ${a.adapterId}/${a.storageId}.`,
|
|
171
|
-
|
|
402
|
+
p
|
|
172
403
|
);
|
|
173
404
|
}
|
|
174
405
|
}),
|
|
175
406
|
r.onMessage(
|
|
176
407
|
"delete-entry",
|
|
177
|
-
async ({ target: a, key:
|
|
178
|
-
const
|
|
179
|
-
(
|
|
408
|
+
async ({ target: a, key: c }) => {
|
|
409
|
+
const i = t.find(
|
|
410
|
+
(p) => p.target.adapterId === a.adapterId && p.target.storageId === a.storageId
|
|
180
411
|
);
|
|
181
|
-
if (!
|
|
412
|
+
if (!i) {
|
|
182
413
|
console.warn(
|
|
183
414
|
`[Rozenite] Storage Plugin: Storage target not found for ${a.adapterId}/${a.storageId}`
|
|
184
415
|
);
|
|
185
416
|
return;
|
|
186
417
|
}
|
|
187
418
|
try {
|
|
188
|
-
await
|
|
189
|
-
} catch (
|
|
419
|
+
await i.delete(c);
|
|
420
|
+
} catch (p) {
|
|
190
421
|
console.warn(
|
|
191
422
|
`[Rozenite] Storage Plugin: Failed to delete entry in ${a.adapterId}/${a.storageId}.`,
|
|
192
|
-
|
|
423
|
+
p
|
|
193
424
|
);
|
|
194
425
|
}
|
|
195
426
|
}
|
|
196
427
|
),
|
|
197
428
|
r.onMessage("get-snapshot", async ({ target: a }) => {
|
|
198
429
|
if (a === "all") {
|
|
199
|
-
await
|
|
430
|
+
await s();
|
|
200
431
|
return;
|
|
201
432
|
}
|
|
202
|
-
await
|
|
433
|
+
await s(`${a.adapterId}:${a.storageId}`);
|
|
203
434
|
})
|
|
204
435
|
];
|
|
205
436
|
return () => {
|
|
206
|
-
|
|
437
|
+
o = !0, n.forEach((a) => a.remove()), d.forEach((a) => a.remove());
|
|
207
438
|
};
|
|
208
|
-
}, [r,
|
|
439
|
+
}, [r, t]), r;
|
|
209
440
|
};
|
|
210
441
|
export {
|
|
211
|
-
|
|
442
|
+
J as useRozeniteStoragePlugin
|
|
212
443
|
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rozenite/storage-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Generic Storage Inspector for Rozenite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/react-native.cjs",
|
|
7
7
|
"module": "./dist/react-native.js",
|
|
8
8
|
"types": "./dist/react-native.d.ts",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@rozenite/plugin-bridge": "1.
|
|
10
|
+
"@rozenite/plugin-bridge": "1.5.1",
|
|
11
|
+
"@rozenite/agent-bridge": "1.5.1"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
13
14
|
"@tanstack/react-table": "^8.21.3",
|
|
@@ -28,8 +29,8 @@
|
|
|
28
29
|
"tailwindcss-animate": "^1.0.7",
|
|
29
30
|
"typescript": "~5.9.3",
|
|
30
31
|
"vite": "^7.3.1",
|
|
31
|
-
"@rozenite/vite-plugin": "1.
|
|
32
|
-
"rozenite": "1.
|
|
32
|
+
"@rozenite/vite-plugin": "1.5.1",
|
|
33
|
+
"rozenite": "1.5.1"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
36
|
"@react-native-async-storage/async-storage": "*",
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
} from '../shared/messaging';
|
|
9
9
|
import type { StorageAdapter } from '../shared/types';
|
|
10
10
|
import { createStorageViews } from './storage-view';
|
|
11
|
+
import { useStorageAgentTools } from './useStorageAgentTools';
|
|
11
12
|
|
|
12
13
|
export type RozeniteStoragePluginOptions = {
|
|
13
14
|
storages: StorageAdapter[];
|
|
@@ -18,6 +19,8 @@ export const useRozeniteStoragePlugin = ({
|
|
|
18
19
|
}: RozeniteStoragePluginOptions) => {
|
|
19
20
|
const views = useMemo(() => createStorageViews(storages), [storages]);
|
|
20
21
|
|
|
22
|
+
useStorageAgentTools(views);
|
|
23
|
+
|
|
21
24
|
const client = useRozeniteDevToolsClient<StorageEventMap>({
|
|
22
25
|
pluginId: '@rozenite/storage-plugin',
|
|
23
26
|
});
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useRozenitePluginAgentTool, type AgentTool } from '@rozenite/agent-bridge';
|
|
3
|
+
import type { StorageEntry, StorageEntryType, StorageEntryValue } from '../shared/types';
|
|
4
|
+
import type { StorageView } from './storage-view';
|
|
5
|
+
|
|
6
|
+
type StorageInput = { adapterId?: string; storageId?: string };
|
|
7
|
+
type KeyInput = StorageInput & { key: string };
|
|
8
|
+
type SetInput = StorageInput & {
|
|
9
|
+
key: string;
|
|
10
|
+
type: StorageEntryType;
|
|
11
|
+
value: unknown;
|
|
12
|
+
};
|
|
13
|
+
type ListEntriesInput = StorageInput & {
|
|
14
|
+
offset?: number;
|
|
15
|
+
limit?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const toolPrefix = '@rozenite/storage-plugin';
|
|
19
|
+
const sharedStorageProperties = {
|
|
20
|
+
adapterId: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description:
|
|
23
|
+
'Storage adapter ID. Required when multiple adapters are configured.',
|
|
24
|
+
},
|
|
25
|
+
storageId: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description:
|
|
28
|
+
'Storage node ID within the adapter. Required when multiple storages are configured.',
|
|
29
|
+
},
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
const listStoragesTool: AgentTool = {
|
|
33
|
+
name: 'list-storages',
|
|
34
|
+
description:
|
|
35
|
+
'List all storage adapters and their storage nodes currently available on the device, including supported entry types and entry counts.',
|
|
36
|
+
inputSchema: { type: 'object', properties: {} },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const listEntriesTool: AgentTool = {
|
|
40
|
+
name: 'list-entries',
|
|
41
|
+
description:
|
|
42
|
+
'List keys in a storage. This call intentionally does not return entry values to keep responses token-efficient.',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
...sharedStorageProperties,
|
|
47
|
+
offset: {
|
|
48
|
+
type: 'number',
|
|
49
|
+
description: 'Pagination offset. Defaults to 0.',
|
|
50
|
+
},
|
|
51
|
+
limit: {
|
|
52
|
+
type: 'number',
|
|
53
|
+
description: 'Pagination size. Defaults to 100.',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const readEntryTool: AgentTool = {
|
|
60
|
+
name: 'read-entry',
|
|
61
|
+
description: 'Read a single storage entry value by key.',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
...sharedStorageProperties,
|
|
66
|
+
key: { type: 'string', description: 'Entry key.' },
|
|
67
|
+
},
|
|
68
|
+
required: ['key'],
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const createEntryTool: AgentTool = {
|
|
73
|
+
name: 'create-entry',
|
|
74
|
+
description: 'Create a new storage entry. Fails if the key already exists.',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
...sharedStorageProperties,
|
|
79
|
+
key: { type: 'string', description: 'Entry key.' },
|
|
80
|
+
type: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
enum: ['string', 'number', 'boolean', 'buffer'],
|
|
83
|
+
description:
|
|
84
|
+
'Entry value type. Must be one of the supported types for the target storage (see list-storages).',
|
|
85
|
+
},
|
|
86
|
+
value: {
|
|
87
|
+
description: 'Entry value matching the provided type.',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
required: ['key', 'type', 'value'],
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const editEntryTool: AgentTool = {
|
|
95
|
+
name: 'edit-entry',
|
|
96
|
+
description: 'Edit an existing storage entry. Fails if the key does not exist.',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
...sharedStorageProperties,
|
|
101
|
+
key: { type: 'string', description: 'Entry key.' },
|
|
102
|
+
type: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
enum: ['string', 'number', 'boolean', 'buffer'],
|
|
105
|
+
description:
|
|
106
|
+
'Entry value type. Must be one of the supported types for the target storage (see list-storages).',
|
|
107
|
+
},
|
|
108
|
+
value: {
|
|
109
|
+
description: 'Entry value matching the provided type.',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
required: ['key', 'type', 'value'],
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const removeEntryTool: AgentTool = {
|
|
117
|
+
name: 'remove-entry',
|
|
118
|
+
description: 'Remove a storage entry by key.',
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
...sharedStorageProperties,
|
|
123
|
+
key: { type: 'string', description: 'Entry key.' },
|
|
124
|
+
},
|
|
125
|
+
required: ['key'],
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const parseValueForType = (
|
|
130
|
+
type: StorageEntryType,
|
|
131
|
+
value: unknown
|
|
132
|
+
): StorageEntryValue => {
|
|
133
|
+
if (type === 'string') {
|
|
134
|
+
if (typeof value !== 'string') {
|
|
135
|
+
throw new Error('Expected string value for type=string');
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (type === 'number') {
|
|
141
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
142
|
+
throw new Error('Expected number value for type=number');
|
|
143
|
+
}
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (type === 'boolean') {
|
|
148
|
+
if (typeof value !== 'boolean') {
|
|
149
|
+
throw new Error('Expected boolean value for type=boolean');
|
|
150
|
+
}
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!Array.isArray(value) || !value.every((item) => Number.isInteger(item))) {
|
|
155
|
+
throw new Error('Expected number[] value for type=buffer');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return value as number[];
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const formatViewRef = (view: StorageView) =>
|
|
162
|
+
`${view.target.adapterId}/${view.target.storageId}`;
|
|
163
|
+
|
|
164
|
+
export const useStorageAgentTools = (views: StorageView[]) => {
|
|
165
|
+
const resolveStorage = useCallback(
|
|
166
|
+
(adapterId?: string, storageId?: string) => {
|
|
167
|
+
if (views.length === 0) {
|
|
168
|
+
throw new Error('No storages are registered.');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (adapterId !== undefined || storageId !== undefined) {
|
|
172
|
+
const selected = views.find(
|
|
173
|
+
(view) =>
|
|
174
|
+
(adapterId === undefined || view.target.adapterId === adapterId) &&
|
|
175
|
+
(storageId === undefined || view.target.storageId === storageId)
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (!selected) {
|
|
179
|
+
const qualifier = [
|
|
180
|
+
adapterId && `adapterId="${adapterId}"`,
|
|
181
|
+
storageId && `storageId="${storageId}"`,
|
|
182
|
+
]
|
|
183
|
+
.filter(Boolean)
|
|
184
|
+
.join(', ');
|
|
185
|
+
throw new Error(
|
|
186
|
+
`No storage matched ${qualifier}. Available: ${views.map(formatViewRef).join(', ')}`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return selected;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (views.length > 1) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Multiple storages detected. Provide adapterId and/or storageId. Available: ${views.map(formatViewRef).join(', ')}`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return views[0];
|
|
200
|
+
},
|
|
201
|
+
[views]
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
useRozenitePluginAgentTool({
|
|
205
|
+
pluginId: toolPrefix,
|
|
206
|
+
tool: listStoragesTool,
|
|
207
|
+
handler: async () => ({
|
|
208
|
+
storages: await Promise.all(
|
|
209
|
+
views.map(async (view) => ({
|
|
210
|
+
adapterId: view.target.adapterId,
|
|
211
|
+
storageId: view.target.storageId,
|
|
212
|
+
adapterName: view.adapterName,
|
|
213
|
+
storageName: view.storageName,
|
|
214
|
+
supportedTypes: view.capabilities.supportedTypes,
|
|
215
|
+
entryCount: (await view.getAllKeys()).length,
|
|
216
|
+
}))
|
|
217
|
+
),
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
useRozenitePluginAgentTool<ListEntriesInput>({
|
|
222
|
+
pluginId: toolPrefix,
|
|
223
|
+
tool: listEntriesTool,
|
|
224
|
+
handler: async ({ adapterId, storageId, offset = 0, limit = 100 }) => {
|
|
225
|
+
const view = resolveStorage(adapterId, storageId);
|
|
226
|
+
const allKeys = await view.getAllKeys();
|
|
227
|
+
const safeOffset = Math.max(0, Math.floor(offset));
|
|
228
|
+
const safeLimit = Math.max(1, Math.floor(limit));
|
|
229
|
+
const keys = allKeys.slice(safeOffset, safeOffset + safeLimit);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
adapterId: view.target.adapterId,
|
|
233
|
+
storageId: view.target.storageId,
|
|
234
|
+
total: allKeys.length,
|
|
235
|
+
offset: safeOffset,
|
|
236
|
+
limit: safeLimit,
|
|
237
|
+
keys,
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
useRozenitePluginAgentTool<
|
|
243
|
+
KeyInput,
|
|
244
|
+
{ adapterId: string; storageId: string; entry: StorageEntry }
|
|
245
|
+
>({
|
|
246
|
+
pluginId: toolPrefix,
|
|
247
|
+
tool: readEntryTool,
|
|
248
|
+
handler: async ({ adapterId, storageId, key }) => {
|
|
249
|
+
const view = resolveStorage(adapterId, storageId);
|
|
250
|
+
const entry = await view.get(key);
|
|
251
|
+
|
|
252
|
+
if (!entry) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`Key "${key}" not found in storage "${formatViewRef(view)}".`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
adapterId: view.target.adapterId,
|
|
260
|
+
storageId: view.target.storageId,
|
|
261
|
+
entry,
|
|
262
|
+
};
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
useRozenitePluginAgentTool<SetInput>({
|
|
267
|
+
pluginId: toolPrefix,
|
|
268
|
+
tool: createEntryTool,
|
|
269
|
+
handler: async ({ adapterId, storageId, key, type, value }) => {
|
|
270
|
+
const view = resolveStorage(adapterId, storageId);
|
|
271
|
+
const existing = await view.get(key);
|
|
272
|
+
|
|
273
|
+
if (existing) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Key "${key}" already exists in storage "${formatViewRef(view)}". Use edit-entry instead.`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const parsedValue = parseValueForType(type, value);
|
|
280
|
+
await view.set({ key, type, value: parsedValue } as StorageEntry);
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
adapterId: view.target.adapterId,
|
|
284
|
+
storageId: view.target.storageId,
|
|
285
|
+
created: true,
|
|
286
|
+
key,
|
|
287
|
+
};
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
useRozenitePluginAgentTool<SetInput>({
|
|
292
|
+
pluginId: toolPrefix,
|
|
293
|
+
tool: editEntryTool,
|
|
294
|
+
handler: async ({ adapterId, storageId, key, type, value }) => {
|
|
295
|
+
const view = resolveStorage(adapterId, storageId);
|
|
296
|
+
const existing = await view.get(key);
|
|
297
|
+
|
|
298
|
+
if (!existing) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Key "${key}" not found in storage "${formatViewRef(view)}". Use create-entry instead.`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const parsedValue = parseValueForType(type, value);
|
|
305
|
+
await view.set({ key, type, value: parsedValue } as StorageEntry);
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
adapterId: view.target.adapterId,
|
|
309
|
+
storageId: view.target.storageId,
|
|
310
|
+
edited: true,
|
|
311
|
+
key,
|
|
312
|
+
};
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
useRozenitePluginAgentTool<KeyInput>({
|
|
317
|
+
pluginId: toolPrefix,
|
|
318
|
+
tool: removeEntryTool,
|
|
319
|
+
handler: async ({ adapterId, storageId, key }) => {
|
|
320
|
+
const view = resolveStorage(adapterId, storageId);
|
|
321
|
+
const existed = !!(await view.get(key));
|
|
322
|
+
await view.delete(key);
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
adapterId: view.target.adapterId,
|
|
326
|
+
storageId: view.target.storageId,
|
|
327
|
+
removed: existed,
|
|
328
|
+
key,
|
|
329
|
+
};
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
};
|