@powerhousedao/reactor-browser 6.2.2-dev.83 → 6.2.2-dev.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{document-model-modules-DMIR-uSD.js → document-model-modules-DfQBNGc-.js} +11 -3
- package/dist/{document-model-modules-DMIR-uSD.js.map → document-model-modules-DfQBNGc-.js.map} +1 -1
- package/dist/{document-operations-BAJGXn6w.js → document-operations-CBExT76y.js} +3 -3
- package/dist/document-operations-CBExT76y.js.map +1 -0
- package/dist/index.d.ts +75 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +348 -18
- package/dist/index.js.map +1 -1
- package/dist/src/ai/index.d.ts +3 -1
- package/dist/src/ai/index.d.ts.map +1 -1
- package/dist/src/ai/index.js +3 -232
- package/dist/src/ai/index.js.map +1 -1
- package/dist/src/graphql-client/entry.js +2 -2
- package/dist/switchboard-D4-_jwk-.js +271 -0
- package/dist/switchboard-D4-_jwk-.js.map +1 -0
- package/package.json +10 -9
- package/dist/document-operations-BAJGXn6w.js.map +0 -1
- package/dist/selected-document-xFBNPc9Q.js +0 -42
- package/dist/selected-document-xFBNPc9Q.js.map +0 -1
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { t as useDocumentById } from "./document-by-id-BSZqTN66.js";
|
|
2
|
+
import { G as DocumentTypeMismatchError, H as ambientRenownTokenProvider, K as NoSelectedDocumentError, s as useSelectedNode, t as useDocumentModelModuleById } from "./document-model-modules-DfQBNGc-.js";
|
|
3
|
+
import { isFileNode } from "@powerhousedao/shared/document-drive";
|
|
4
|
+
import { DriveCollectionId, ModuleNotFoundError } from "@powerhousedao/reactor";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region src/hooks/document-of-type.ts
|
|
7
|
+
/** Returns a document of a specific type, throws an error if the found document has a different type */
|
|
8
|
+
function useDocumentOfType(documentId, documentType) {
|
|
9
|
+
const [document, dispatch] = useDocumentById(documentId);
|
|
10
|
+
const documentModelModule = useDocumentModelModuleById(documentType);
|
|
11
|
+
if (!documentId || !documentType) return [];
|
|
12
|
+
if (!document) throw new Error(`Document not found: ${documentId}`);
|
|
13
|
+
if (!documentModelModule) throw new ModuleNotFoundError(documentType);
|
|
14
|
+
if (document.header.documentType !== documentType) throw new DocumentTypeMismatchError(documentId, documentType, document.header.documentType);
|
|
15
|
+
return [document, dispatch];
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/hooks/selected-document.ts
|
|
19
|
+
/** Returns the selected document id */
|
|
20
|
+
function useSelectedDocumentId() {
|
|
21
|
+
const selectedNode = useSelectedNode();
|
|
22
|
+
return selectedNode && isFileNode(selectedNode) ? selectedNode.id : void 0;
|
|
23
|
+
}
|
|
24
|
+
/** Returns the selected document. */
|
|
25
|
+
function useSelectedDocument() {
|
|
26
|
+
const [document, dispatch] = useDocumentById(useSelectedDocumentId());
|
|
27
|
+
if (!document) throw new NoSelectedDocumentError();
|
|
28
|
+
return [document, dispatch];
|
|
29
|
+
}
|
|
30
|
+
/** Returns the selected document. */
|
|
31
|
+
function useSelectedDocumentSafe() {
|
|
32
|
+
return useDocumentById(useSelectedDocumentId());
|
|
33
|
+
}
|
|
34
|
+
function useSelectedDocumentOfType(documentType) {
|
|
35
|
+
const documentId = useSelectedDocumentId();
|
|
36
|
+
if (!documentType) return [];
|
|
37
|
+
if (!documentId) throw new NoSelectedDocumentError();
|
|
38
|
+
return useDocumentOfType(documentId, documentType);
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/ai/switchboard.ts
|
|
42
|
+
/**
|
|
43
|
+
* Reads the tab-side sync manager from the browser global. Returns
|
|
44
|
+
* `undefined` in non-browser environments or before the reactor client
|
|
45
|
+
* module is initialized.
|
|
46
|
+
*/
|
|
47
|
+
function getBrowserSyncManager() {
|
|
48
|
+
if (typeof window === "undefined") return;
|
|
49
|
+
return window.ph?.reactorClientModule?.reactorModule?.syncModule?.syncManager;
|
|
50
|
+
}
|
|
51
|
+
/** Suffix of the per-drive GraphQL channel endpoint the switchboard serves. */
|
|
52
|
+
const GQL_CHANNEL_SUFFIX = "/graphql/r";
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the switchboard that serves a drive, if any.
|
|
55
|
+
*
|
|
56
|
+
* Every remote drive stores its switchboard's exact GraphQL channel
|
|
57
|
+
* endpoint in its sync remote — `channelConfig = { type: "gql",
|
|
58
|
+
* parameters: { url: "<origin>/graphql/r" } }` — and the switchboard base
|
|
59
|
+
* is that URL minus the `/graphql/r` suffix. Deriving it per drive (rather
|
|
60
|
+
* than from a global setting) is what keeps multi-switchboard deployments
|
|
61
|
+
* correct. Drives that are not synced to a remote switchboard (local
|
|
62
|
+
* drives, other channel types, unexpected shapes) return `undefined`.
|
|
63
|
+
*/
|
|
64
|
+
function resolveDriveSwitchboard(driveId, syncManager) {
|
|
65
|
+
const manager = syncManager ?? getBrowserSyncManager();
|
|
66
|
+
if (!driveId || !manager) return;
|
|
67
|
+
const collectionId = DriveCollectionId.forDrive(driveId);
|
|
68
|
+
const remote = manager.list().find((r) => r.meta.collectionId.equals(collectionId));
|
|
69
|
+
if (!remote) return;
|
|
70
|
+
const { channelConfig } = remote.meta;
|
|
71
|
+
if (channelConfig.type !== "gql") return;
|
|
72
|
+
const url = channelConfig.parameters.url;
|
|
73
|
+
if (typeof url !== "string" || !url.endsWith("/graphql/r")) return;
|
|
74
|
+
const switchboardUrl = url.slice(0, -10);
|
|
75
|
+
return {
|
|
76
|
+
switchboardUrl,
|
|
77
|
+
graphqlUrl: `${switchboardUrl}/graphql`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Introspection over the switchboard supergraph's root query and mutation
|
|
82
|
+
* fields. Types are requested with three levels of `ofType` nesting so
|
|
83
|
+
* that common shapes like `[X!]!` (LIST(NON_NULL(LIST(X)))) render fully.
|
|
84
|
+
*/
|
|
85
|
+
const SWITCHBOARD_INTROSPECTION_QUERY = `
|
|
86
|
+
query SwitchboardIntrospection {
|
|
87
|
+
__schema {
|
|
88
|
+
queryType {
|
|
89
|
+
fields {
|
|
90
|
+
name
|
|
91
|
+
description
|
|
92
|
+
args {
|
|
93
|
+
name
|
|
94
|
+
type {
|
|
95
|
+
kind
|
|
96
|
+
name
|
|
97
|
+
ofType {
|
|
98
|
+
kind
|
|
99
|
+
name
|
|
100
|
+
ofType {
|
|
101
|
+
kind
|
|
102
|
+
name
|
|
103
|
+
ofType {
|
|
104
|
+
kind
|
|
105
|
+
name
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
type {
|
|
112
|
+
kind
|
|
113
|
+
name
|
|
114
|
+
ofType {
|
|
115
|
+
kind
|
|
116
|
+
name
|
|
117
|
+
ofType {
|
|
118
|
+
kind
|
|
119
|
+
name
|
|
120
|
+
ofType {
|
|
121
|
+
kind
|
|
122
|
+
name
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
mutationType {
|
|
130
|
+
fields {
|
|
131
|
+
name
|
|
132
|
+
description
|
|
133
|
+
args {
|
|
134
|
+
name
|
|
135
|
+
type {
|
|
136
|
+
kind
|
|
137
|
+
name
|
|
138
|
+
ofType {
|
|
139
|
+
kind
|
|
140
|
+
name
|
|
141
|
+
ofType {
|
|
142
|
+
kind
|
|
143
|
+
name
|
|
144
|
+
ofType {
|
|
145
|
+
kind
|
|
146
|
+
name
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
type {
|
|
153
|
+
kind
|
|
154
|
+
name
|
|
155
|
+
ofType {
|
|
156
|
+
kind
|
|
157
|
+
name
|
|
158
|
+
ofType {
|
|
159
|
+
kind
|
|
160
|
+
name
|
|
161
|
+
ofType {
|
|
162
|
+
kind
|
|
163
|
+
name
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
/**
|
|
174
|
+
* Renders an introspected type reference as a GraphQL type string.
|
|
175
|
+
* Recursion is bounded by the `ofType` nesting of the query, so it always
|
|
176
|
+
* terminates.
|
|
177
|
+
*/
|
|
178
|
+
function renderTypeRef(type) {
|
|
179
|
+
if (!type) return "Unknown";
|
|
180
|
+
switch (type.kind) {
|
|
181
|
+
case "NON_NULL": return `${renderTypeRef(type.ofType)}!`;
|
|
182
|
+
case "LIST": return `[${renderTypeRef(type.ofType)}]`;
|
|
183
|
+
default: return type.name ?? "Unknown";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Cap on the number of fields reported per root type. */
|
|
187
|
+
const MAX_FIELDS_PER_ROOT = 200;
|
|
188
|
+
function summarizeFields(fields) {
|
|
189
|
+
const list = fields ?? [];
|
|
190
|
+
return {
|
|
191
|
+
fields: list.slice(0, MAX_FIELDS_PER_ROOT).map((field) => ({
|
|
192
|
+
name: field.name,
|
|
193
|
+
...field.description ? { description: field.description } : {},
|
|
194
|
+
args: field.args.map((arg) => `${arg.name}: ${renderTypeRef(arg.type)}`).join(", "),
|
|
195
|
+
returns: renderTypeRef(field.type)
|
|
196
|
+
})),
|
|
197
|
+
truncated: list.length > MAX_FIELDS_PER_ROOT
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Reduces a GraphQL `__schema` introspection payload (the `data.__schema`
|
|
202
|
+
* object) to a compact summary of its root query and mutation fields.
|
|
203
|
+
* Accepts `unknown` because the payload arrives over the wire.
|
|
204
|
+
*/
|
|
205
|
+
function summarizeIntrospectionSchema(schema) {
|
|
206
|
+
const introspection = schema ?? {};
|
|
207
|
+
const queries = summarizeFields(introspection.queryType?.fields);
|
|
208
|
+
const mutations = summarizeFields(introspection.mutationType?.fields);
|
|
209
|
+
return {
|
|
210
|
+
queries: queries.fields,
|
|
211
|
+
mutations: mutations.fields,
|
|
212
|
+
truncated: queries.truncated || mutations.truncated
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const SWITCHBOARD_SCHEMA_TOOL_NAME = "getSwitchboardSchema";
|
|
216
|
+
/**
|
|
217
|
+
* Creates the read-only `getSwitchboardSchema` tool: it introspects the
|
|
218
|
+
* GraphQL supergraph of the switchboard serving a drive and returns a
|
|
219
|
+
* summary of its root queries and mutations. It reads the schema only and
|
|
220
|
+
* never modifies data.
|
|
221
|
+
*/
|
|
222
|
+
function createSwitchboardSchemaTool(deps) {
|
|
223
|
+
const getSyncManager = deps?.getSyncManager ?? getBrowserSyncManager;
|
|
224
|
+
const getSelectedDriveId = deps?.getSelectedDriveId ?? (() => typeof window === "undefined" ? void 0 : window.ph?.selectedDriveId);
|
|
225
|
+
const tokenProvider = deps?.tokenProvider ?? ambientRenownTokenProvider;
|
|
226
|
+
const fetchImpl = deps?.fetchImpl ?? fetch;
|
|
227
|
+
const timeoutMs = deps?.timeoutMs ?? 1e4;
|
|
228
|
+
return {
|
|
229
|
+
name: SWITCHBOARD_SCHEMA_TOOL_NAME,
|
|
230
|
+
description: "Introspect the GraphQL endpoint of the switchboard serving a drive and list the queries and mutations it exposes (reactor operations, document-model read models, package subgraphs). Read-only: it reads the schema only and never modifies data. Omit driveId to introspect the currently selected drive.",
|
|
231
|
+
inputSchema: { driveId: z.string().describe("Drive to introspect. Omit to use the currently selected drive.").optional() },
|
|
232
|
+
annotations: { readOnlyHint: true },
|
|
233
|
+
callback: async (input) => {
|
|
234
|
+
const driveId = input?.driveId ?? getSelectedDriveId();
|
|
235
|
+
const sb = resolveDriveSwitchboard(driveId, getSyncManager());
|
|
236
|
+
if (!sb) throw new Error(driveId ? `Drive "${driveId}" has no switchboard: it is not synced to a remote switchboard.` : "No drive is currently selected and no driveId was given.");
|
|
237
|
+
const token = await tokenProvider();
|
|
238
|
+
let response;
|
|
239
|
+
try {
|
|
240
|
+
response = await fetchImpl(sb.graphqlUrl, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: {
|
|
243
|
+
"content-type": "application/json",
|
|
244
|
+
...token ? { authorization: `Bearer ${token}` } : {}
|
|
245
|
+
},
|
|
246
|
+
body: JSON.stringify({ query: SWITCHBOARD_INTROSPECTION_QUERY }),
|
|
247
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
248
|
+
});
|
|
249
|
+
} catch (error) {
|
|
250
|
+
throw new Error(`Switchboard at ${sb.graphqlUrl} is not reachable: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
251
|
+
}
|
|
252
|
+
if (response.status === 401 || response.status === 403) throw new Error(`Switchboard at ${sb.graphqlUrl} refused the request (HTTP ${response.status}). Sign in to the switchboard (Renown) and try again.`);
|
|
253
|
+
if (!response.ok) throw new Error(`Switchboard introspection failed: HTTP ${response.status} ${response.statusText} from ${sb.graphqlUrl}`);
|
|
254
|
+
const body = await response.json();
|
|
255
|
+
const schemaErrors = body?.errors;
|
|
256
|
+
if (schemaErrors && schemaErrors.length > 0) throw new Error(schemaErrors.map((e) => e.message ?? "unknown error").join("; "));
|
|
257
|
+
const schema = body?.data?.__schema;
|
|
258
|
+
if (!schema?.queryType) throw new Error(`Switchboard at ${sb.graphqlUrl} did not return a GraphQL schema.`);
|
|
259
|
+
const summary = summarizeIntrospectionSchema(schema);
|
|
260
|
+
return {
|
|
261
|
+
switchboardUrl: sb.switchboardUrl,
|
|
262
|
+
graphqlUrl: sb.graphqlUrl,
|
|
263
|
+
...summary
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
export { getBrowserSyncManager as a, useSelectedDocument as c, useSelectedDocumentSafe as d, useDocumentOfType as f, createSwitchboardSchemaTool as i, useSelectedDocumentId as l, SWITCHBOARD_INTROSPECTION_QUERY as n, resolveDriveSwitchboard as o, SWITCHBOARD_SCHEMA_TOOL_NAME as r, summarizeIntrospectionSchema as s, GQL_CHANNEL_SUFFIX as t, useSelectedDocumentOfType as u };
|
|
270
|
+
|
|
271
|
+
//# sourceMappingURL=switchboard-D4-_jwk-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"switchboard-D4-_jwk-.js","names":[],"sources":["../src/hooks/document-of-type.ts","../src/hooks/selected-document.ts","../src/ai/switchboard.ts"],"sourcesContent":["import { ModuleNotFoundError } from \"@powerhousedao/reactor\";\nimport type { DocumentDispatch } from \"@powerhousedao/reactor-browser\";\nimport type { Action, PHDocument } from \"@powerhousedao/shared/document-model\";\nimport { DocumentTypeMismatchError } from \"../errors.js\";\nimport { useDocumentById } from \"./document-by-id.js\";\nimport { useDocumentModelModuleById } from \"./document-model-modules.js\";\n\n/** Returns a document of a specific type, throws an error if the found document has a different type */\nexport function useDocumentOfType<\n TDocument extends PHDocument,\n TAction extends Action,\n>(\n documentId: string | null | undefined,\n documentType: string | null | undefined,\n) {\n const [document, dispatch] = useDocumentById(documentId);\n const documentModelModule = useDocumentModelModuleById(documentType);\n\n if (!documentId || !documentType) return [];\n\n if (!document) {\n throw new Error(`Document not found: ${documentId}`);\n }\n if (!documentModelModule) {\n throw new ModuleNotFoundError(documentType);\n }\n\n if (document.header.documentType !== documentType) {\n throw new DocumentTypeMismatchError(\n documentId,\n documentType,\n document.header.documentType,\n );\n }\n\n return [document, dispatch] as [TDocument, DocumentDispatch<TAction>];\n}\n","import type { DocumentDispatch } from \"@powerhousedao/reactor-browser\";\nimport { isFileNode } from \"@powerhousedao/shared/document-drive\";\nimport type { Action, PHDocument } from \"@powerhousedao/shared/document-model\";\nimport { NoSelectedDocumentError } from \"../errors.js\";\nimport type { DispatchFn, UseDispatchResult } from \"./dispatch.js\";\nimport { useDocumentById } from \"./document-by-id.js\";\nimport { useDocumentOfType } from \"./document-of-type.js\";\nimport { useSelectedNode } from \"./selected-node.js\";\n\n/** Returns the selected document id */\nexport function useSelectedDocumentId(): string | undefined {\n const selectedNode = useSelectedNode();\n return selectedNode && isFileNode(selectedNode) ? selectedNode.id : undefined;\n}\n\n/** Returns the selected document. */\nexport function useSelectedDocument(): readonly [\n PHDocument,\n DispatchFn<Action>,\n] {\n const selectedDocumentId = useSelectedDocumentId();\n const [document, dispatch] = useDocumentById(selectedDocumentId);\n if (!document) {\n throw new NoSelectedDocumentError();\n }\n return [document, dispatch] as const;\n}\n\n/** Returns the selected document. */\nexport function useSelectedDocumentSafe(): UseDispatchResult<\n PHDocument,\n Action\n> {\n const selectedDocumentId = useSelectedDocumentId();\n return useDocumentById(selectedDocumentId);\n}\n\n/** Returns the selected document of a specific type, throws an error if the found document has a different type */\nexport function useSelectedDocumentOfType(\n documentType: null | undefined,\n): never[];\nexport function useSelectedDocumentOfType<\n TDocument extends PHDocument,\n TAction extends Action,\n>(documentType: string): [TDocument, DocumentDispatch<TAction>];\nexport function useSelectedDocumentOfType<\n TDocument extends PHDocument,\n TAction extends Action,\n>(\n documentType: string | null | undefined,\n): never[] | [TDocument, DocumentDispatch<TAction>] {\n const documentId = useSelectedDocumentId();\n\n if (!documentType) {\n return [];\n }\n if (!documentId) {\n throw new NoSelectedDocumentError();\n }\n return useDocumentOfType<TDocument, TAction>(documentId, documentType);\n}\n","import { DriveCollectionId, type ISyncManager } from \"@powerhousedao/reactor\";\nimport { z } from \"zod\";\nimport { ambientRenownTokenProvider } from \"../graphql-client/auth.js\";\nimport type { AiToolDescriptor } from \"./types.js\";\n\n/**\n * The switchboard serving a remote drive: the base URL the browser talks to\n * and its GraphQL supergraph endpoint.\n */\nexport interface DriveSwitchboard {\n /** Switchboard base URL (e.g. `http://localhost:4001`). */\n switchboardUrl: string;\n /** GraphQL supergraph endpoint (`<base>/graphql`). */\n graphqlUrl: string;\n}\n\n/**\n * Reads the tab-side sync manager from the browser global. Returns\n * `undefined` in non-browser environments or before the reactor client\n * module is initialized.\n */\nexport function getBrowserSyncManager(): ISyncManager | undefined {\n if (typeof window === \"undefined\") {\n return undefined;\n }\n return window.ph?.reactorClientModule?.reactorModule?.syncModule?.syncManager;\n}\n\n/** Suffix of the per-drive GraphQL channel endpoint the switchboard serves. */\nexport const GQL_CHANNEL_SUFFIX = \"/graphql/r\";\n\n/**\n * Resolves the switchboard that serves a drive, if any.\n *\n * Every remote drive stores its switchboard's exact GraphQL channel\n * endpoint in its sync remote — `channelConfig = { type: \"gql\",\n * parameters: { url: \"<origin>/graphql/r\" } }` — and the switchboard base\n * is that URL minus the `/graphql/r` suffix. Deriving it per drive (rather\n * than from a global setting) is what keeps multi-switchboard deployments\n * correct. Drives that are not synced to a remote switchboard (local\n * drives, other channel types, unexpected shapes) return `undefined`.\n */\nexport function resolveDriveSwitchboard(\n driveId: string | undefined,\n syncManager?: ISyncManager,\n): DriveSwitchboard | undefined {\n const manager = syncManager ?? getBrowserSyncManager();\n if (!driveId || !manager) {\n return undefined;\n }\n const collectionId = DriveCollectionId.forDrive(driveId);\n const remote = manager\n .list()\n .find((r) => r.meta.collectionId.equals(collectionId));\n if (!remote) {\n return undefined;\n }\n const { channelConfig } = remote.meta;\n if (channelConfig.type !== \"gql\") {\n return undefined;\n }\n const url = channelConfig.parameters.url;\n if (typeof url !== \"string\" || !url.endsWith(GQL_CHANNEL_SUFFIX)) {\n return undefined;\n }\n const switchboardUrl = url.slice(0, -GQL_CHANNEL_SUFFIX.length);\n return { switchboardUrl, graphqlUrl: `${switchboardUrl}/graphql` };\n}\n\n/** A bounded introspected GraphQL type reference (see the query below). */\ntype IntrospectedTypeRef = {\n kind: string;\n name: string | null;\n ofType?: IntrospectedTypeRef | null;\n};\n\ntype IntrospectedField = {\n name: string;\n description: string | null;\n args: Array<{ name: string; type: IntrospectedTypeRef }>;\n type: IntrospectedTypeRef;\n};\n\ntype IntrospectedRootType = {\n fields: IntrospectedField[] | null;\n} | null;\n\ntype IntrospectedSchema = {\n queryType?: IntrospectedRootType;\n mutationType?: IntrospectedRootType;\n};\n\n/**\n * Introspection over the switchboard supergraph's root query and mutation\n * fields. Types are requested with three levels of `ofType` nesting so\n * that common shapes like `[X!]!` (LIST(NON_NULL(LIST(X)))) render fully.\n */\nexport const SWITCHBOARD_INTROSPECTION_QUERY: string = /* GraphQL */ `\n query SwitchboardIntrospection {\n __schema {\n queryType {\n fields {\n name\n description\n args {\n name\n type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n mutationType {\n fields {\n name\n description\n args {\n name\n type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n }\n`;\n\n/**\n * Renders an introspected type reference as a GraphQL type string.\n * Recursion is bounded by the `ofType` nesting of the query, so it always\n * terminates.\n */\nfunction renderTypeRef(type: IntrospectedTypeRef | null | undefined): string {\n if (!type) {\n return \"Unknown\";\n }\n switch (type.kind) {\n case \"NON_NULL\":\n return `${renderTypeRef(type.ofType)}!`;\n case \"LIST\":\n return `[${renderTypeRef(type.ofType)}]`;\n default:\n return type.name ?? \"Unknown\";\n }\n}\n\n/** One root query or mutation field, reduced to a prompt-friendly line. */\nexport type SchemaFieldSummary = {\n name: string;\n description?: string;\n args: string;\n returns: string;\n};\n\n/** Cap on the number of fields reported per root type. */\nconst MAX_FIELDS_PER_ROOT = 200;\n\nfunction summarizeFields(fields: IntrospectedField[] | null | undefined): {\n fields: SchemaFieldSummary[];\n truncated: boolean;\n} {\n const list = fields ?? [];\n return {\n fields: list.slice(0, MAX_FIELDS_PER_ROOT).map((field) => ({\n name: field.name,\n ...(field.description ? { description: field.description } : {}),\n args: field.args\n .map((arg) => `${arg.name}: ${renderTypeRef(arg.type)}`)\n .join(\", \"),\n returns: renderTypeRef(field.type),\n })),\n truncated: list.length > MAX_FIELDS_PER_ROOT,\n };\n}\n\n/**\n * Reduces a GraphQL `__schema` introspection payload (the `data.__schema`\n * object) to a compact summary of its root query and mutation fields.\n * Accepts `unknown` because the payload arrives over the wire.\n */\nexport function summarizeIntrospectionSchema(schema: unknown): {\n queries: SchemaFieldSummary[];\n mutations: SchemaFieldSummary[];\n truncated: boolean;\n} {\n const introspection = (schema ?? {}) as IntrospectedSchema;\n const queries = summarizeFields(introspection.queryType?.fields);\n const mutations = summarizeFields(introspection.mutationType?.fields);\n return {\n queries: queries.fields,\n mutations: mutations.fields,\n truncated: queries.truncated || mutations.truncated,\n };\n}\n\nexport const SWITCHBOARD_SCHEMA_TOOL_NAME = \"getSwitchboardSchema\";\n\n/** Test seams for {@link createSwitchboardSchemaTool}. */\nexport interface SwitchboardToolDeps {\n getSyncManager?: () => ISyncManager | undefined;\n getSelectedDriveId?: () => string | undefined;\n tokenProvider?: () => Promise<string | undefined>;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n}\n\n/**\n * Creates the read-only `getSwitchboardSchema` tool: it introspects the\n * GraphQL supergraph of the switchboard serving a drive and returns a\n * summary of its root queries and mutations. It reads the schema only and\n * never modifies data.\n */\nexport function createSwitchboardSchemaTool(\n deps?: SwitchboardToolDeps,\n): AiToolDescriptor {\n const getSyncManager = deps?.getSyncManager ?? getBrowserSyncManager;\n const getSelectedDriveId =\n deps?.getSelectedDriveId ??\n ((): string | undefined =>\n typeof window === \"undefined\" ? undefined : window.ph?.selectedDriveId);\n const tokenProvider = deps?.tokenProvider ?? ambientRenownTokenProvider;\n const fetchImpl = deps?.fetchImpl ?? fetch;\n const timeoutMs = deps?.timeoutMs ?? 10_000;\n\n return {\n name: SWITCHBOARD_SCHEMA_TOOL_NAME,\n description:\n \"Introspect the GraphQL endpoint of the switchboard serving a drive and list the queries and mutations it exposes (reactor operations, document-model read models, package subgraphs). Read-only: it reads the schema only and never modifies data. Omit driveId to introspect the currently selected drive.\",\n inputSchema: {\n driveId: z\n .string()\n .describe(\n \"Drive to introspect. Omit to use the currently selected drive.\",\n )\n .optional(),\n },\n annotations: { readOnlyHint: true },\n callback: async (input: { driveId?: string } | undefined) => {\n const driveId = input?.driveId ?? getSelectedDriveId();\n const sb = resolveDriveSwitchboard(driveId, getSyncManager());\n if (!sb) {\n throw new Error(\n driveId\n ? `Drive \"${driveId}\" has no switchboard: it is not synced to a remote switchboard.`\n : \"No drive is currently selected and no driveId was given.\",\n );\n }\n\n const token = await tokenProvider();\n let response: Response;\n try {\n response = await fetchImpl(sb.graphqlUrl, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify({ query: SWITCHBOARD_INTROSPECTION_QUERY }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (error) {\n throw new Error(\n `Switchboard at ${sb.graphqlUrl} is not reachable: ${\n error instanceof Error ? error.message : String(error)\n }`,\n { cause: error },\n );\n }\n\n if (response.status === 401 || response.status === 403) {\n throw new Error(\n `Switchboard at ${sb.graphqlUrl} refused the request (HTTP ${response.status}). Sign in to the switchboard (Renown) and try again.`,\n );\n }\n if (!response.ok) {\n throw new Error(\n `Switchboard introspection failed: HTTP ${response.status} ${response.statusText} from ${sb.graphqlUrl}`,\n );\n }\n\n const body = (await response.json()) as {\n errors?: Array<{ message?: string }>;\n data?: { __schema?: unknown } | null;\n } | null;\n const schemaErrors = body?.errors;\n if (schemaErrors && schemaErrors.length > 0) {\n throw new Error(\n schemaErrors.map((e) => e.message ?? \"unknown error\").join(\"; \"),\n );\n }\n const schema = body?.data?.__schema as\n | IntrospectedSchema\n | null\n | undefined;\n if (!schema?.queryType) {\n throw new Error(\n `Switchboard at ${sb.graphqlUrl} did not return a GraphQL schema.`,\n );\n }\n\n const summary = summarizeIntrospectionSchema(schema);\n return {\n switchboardUrl: sb.switchboardUrl,\n graphqlUrl: sb.graphqlUrl,\n ...summary,\n };\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/reactor-browser",
|
|
3
|
-
"version": "6.2.2-dev.
|
|
3
|
+
"version": "6.2.2-dev.85",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -64,17 +64,17 @@
|
|
|
64
64
|
"sideEffects": false,
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@ai-sdk/openai-compatible": "3.0.37",
|
|
67
|
-
"@powerhousedao/analytics-engine-browser": "6.2.2-dev.
|
|
68
|
-
"@powerhousedao/analytics-engine-core": "6.2.2-dev.
|
|
69
|
-
"@powerhousedao/reactor": "6.2.2-dev.
|
|
70
|
-
"@powerhousedao/reactor-attachments": "6.2.2-dev.
|
|
71
|
-
"@powerhousedao/reactor-drive": "6.2.2-dev.
|
|
72
|
-
"@powerhousedao/shared": "6.2.2-dev.
|
|
73
|
-
"@renown/sdk": "6.2.2-dev.
|
|
67
|
+
"@powerhousedao/analytics-engine-browser": "6.2.2-dev.85",
|
|
68
|
+
"@powerhousedao/analytics-engine-core": "6.2.2-dev.85",
|
|
69
|
+
"@powerhousedao/reactor": "6.2.2-dev.85",
|
|
70
|
+
"@powerhousedao/reactor-attachments": "6.2.2-dev.85",
|
|
71
|
+
"@powerhousedao/reactor-drive": "6.2.2-dev.85",
|
|
72
|
+
"@powerhousedao/shared": "6.2.2-dev.85",
|
|
73
|
+
"@renown/sdk": "6.2.2-dev.85",
|
|
74
74
|
"@tanstack/react-query": "^5.49.2",
|
|
75
75
|
"ai": "7.0.83",
|
|
76
76
|
"change-case": "5.4.4",
|
|
77
|
-
"document-model": "6.2.2-dev.
|
|
77
|
+
"document-model": "6.2.2-dev.85",
|
|
78
78
|
"graphql-request": "7.4.0",
|
|
79
79
|
"graphql-ws": "6.0.7",
|
|
80
80
|
"kysely": "0.28.16",
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
"@vitest/browser": "4.1.1",
|
|
108
108
|
"@vitest/browser-playwright": "4.1.1",
|
|
109
109
|
"fast-deep-equal": "3.1.3",
|
|
110
|
+
"fflate": "^0.8.2",
|
|
110
111
|
"graphql": "^16",
|
|
111
112
|
"graphql-tag": "^2",
|
|
112
113
|
"playwright": "1.60.0",
|