busabase-sdk 0.9.3 → 0.9.5
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/index.d.ts +12567 -5595
- package/dist/index.js +1046 -313
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -4,145 +4,6 @@ import { oc, eventIterator } from '@orpc/contract';
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
6
6
|
// src/client.ts
|
|
7
|
-
var AttachmentMetadataSchema = z.record(z.string(), z.unknown());
|
|
8
|
-
z.object({
|
|
9
|
-
id: z.string(),
|
|
10
|
-
url: z.string(),
|
|
11
|
-
fileName: z.string(),
|
|
12
|
-
mimeType: z.string(),
|
|
13
|
-
size: z.number().int().nonnegative()
|
|
14
|
-
});
|
|
15
|
-
var RequestUploadUrlInputSchema = z.object({
|
|
16
|
-
fileName: z.string().min(1).max(255),
|
|
17
|
-
mimeType: z.string().min(1),
|
|
18
|
-
sizeBytes: z.number().int().positive(),
|
|
19
|
-
spaceId: z.string().optional(),
|
|
20
|
-
context: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(100).optional(),
|
|
21
|
-
/** Content fingerprint (e.g. "sha256:<hex>") for dedup; computed client-side. */
|
|
22
|
-
contentHash: z.string().max(80).optional()
|
|
23
|
-
});
|
|
24
|
-
var ConfirmUploadInputSchema = z.object({
|
|
25
|
-
storageKey: z.string().min(1),
|
|
26
|
-
fileName: z.string().min(1).max(255),
|
|
27
|
-
mimeType: z.string().min(1),
|
|
28
|
-
sizeBytes: z.number().int().positive(),
|
|
29
|
-
spaceId: z.string().optional(),
|
|
30
|
-
context: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(100).optional(),
|
|
31
|
-
metadata: AttachmentMetadataSchema.optional(),
|
|
32
|
-
/** Content fingerprint (e.g. "sha256:<hex>") persisted for dedup. */
|
|
33
|
-
contentHash: z.string().max(80).optional()
|
|
34
|
-
});
|
|
35
|
-
var RequestUploadUrlVOSchema = z.object({
|
|
36
|
-
uploadUrl: z.string(),
|
|
37
|
-
storageKey: z.string(),
|
|
38
|
-
publicUrl: z.string(),
|
|
39
|
-
expiresIn: z.number(),
|
|
40
|
-
/**
|
|
41
|
-
* True when an identical file (same contentHash, same scope) already exists.
|
|
42
|
-
* The client should SKIP the byte upload and the confirm step, and use
|
|
43
|
-
* `attachmentId`/`publicUrl` directly. `uploadUrl` is empty in this case.
|
|
44
|
-
*/
|
|
45
|
-
duplicate: z.boolean().optional(),
|
|
46
|
-
/** Existing attachment id — present only when `duplicate` is true. */
|
|
47
|
-
attachmentId: z.string().optional(),
|
|
48
|
-
/** Host-specific logical asset id, when the app maps uploads into an Asset library. */
|
|
49
|
-
assetId: z.string().optional()
|
|
50
|
-
});
|
|
51
|
-
var ConfirmUploadVOSchema = z.object({
|
|
52
|
-
success: z.boolean(),
|
|
53
|
-
attachmentId: z.string(),
|
|
54
|
-
/** Host-specific logical asset id, when the app maps uploads into an Asset library. */
|
|
55
|
-
assetId: z.string().optional(),
|
|
56
|
-
storageKey: z.string(),
|
|
57
|
-
publicUrl: z.string()
|
|
58
|
-
});
|
|
59
|
-
var AssetVOSchema = z.object({
|
|
60
|
-
id: z.string(),
|
|
61
|
-
attachmentId: z.string(),
|
|
62
|
-
name: z.string(),
|
|
63
|
-
contentKind: z.enum(["text", "binary"]),
|
|
64
|
-
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
65
|
-
fileName: z.string(),
|
|
66
|
-
mimeType: z.string(),
|
|
67
|
-
size: z.number().int().nonnegative(),
|
|
68
|
-
url: z.string(),
|
|
69
|
-
contentHash: z.string().nullable(),
|
|
70
|
-
/** How many places reference this asset (Base records + Doc bodies). */
|
|
71
|
-
usageCount: z.number().int().nonnegative(),
|
|
72
|
-
createdAt: z.string()
|
|
73
|
-
});
|
|
74
|
-
var AssetUsageVOSchema = z.object({
|
|
75
|
-
ownerType: z.enum(["drive", "skill", "base", "doc", "file_node"]),
|
|
76
|
-
nodeId: z.string(),
|
|
77
|
-
nodeName: z.string(),
|
|
78
|
-
nodeType: z.string(),
|
|
79
|
-
/** Node slug, so the UI can link to the owning Base/Doc (`/{type}/{slug}`). */
|
|
80
|
-
nodeSlug: z.string(),
|
|
81
|
-
/** Drive/Skill mounted path, or null when the usage is not path-based. */
|
|
82
|
-
path: z.string().nullable(),
|
|
83
|
-
/** Base record id, or null for whole-node usages (e.g. a Doc body). */
|
|
84
|
-
recordId: z.string().nullable(),
|
|
85
|
-
/** Attachment field slug, or null for whole-node usages. */
|
|
86
|
-
fieldSlug: z.string().nullable(),
|
|
87
|
-
/** Doc block id, or null when the usage is not block-based. */
|
|
88
|
-
blockId: z.string().nullable(),
|
|
89
|
-
createdAt: z.string()
|
|
90
|
-
});
|
|
91
|
-
var AssetDetailVOSchema = z.object({
|
|
92
|
-
asset: AssetVOSchema,
|
|
93
|
-
usages: z.array(AssetUsageVOSchema)
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// ../../packages/busabase-contract/src/domains/assets/contract.ts
|
|
97
|
-
var UpdateAssetMetadataInputSchema = z.object({
|
|
98
|
-
assetId: z.string(),
|
|
99
|
-
metadata: z.record(z.string(), z.unknown()),
|
|
100
|
-
mode: z.enum(["merge", "replace"]).optional().default("merge")
|
|
101
|
-
});
|
|
102
|
-
var assetsContract = {
|
|
103
|
-
createUploadUrl: oc.route({
|
|
104
|
-
method: "POST",
|
|
105
|
-
path: "/assets/upload-urls",
|
|
106
|
-
tags: ["Assets"],
|
|
107
|
-
summary: "Request asset upload URL",
|
|
108
|
-
successDescription: "Presigned (or dev) upload URL plus the public URL and asset id when identical bytes are already in the library."
|
|
109
|
-
}).input(RequestUploadUrlInputSchema).output(RequestUploadUrlVOSchema),
|
|
110
|
-
confirm: oc.route({
|
|
111
|
-
method: "POST",
|
|
112
|
-
path: "/assets/confirmations",
|
|
113
|
-
tags: ["Assets"],
|
|
114
|
-
summary: "Confirm asset upload",
|
|
115
|
-
successDescription: "Recorded the file and ensured its Busabase Asset library entry."
|
|
116
|
-
}).input(ConfirmUploadInputSchema).output(ConfirmUploadVOSchema),
|
|
117
|
-
list: oc.route({
|
|
118
|
-
method: "GET",
|
|
119
|
-
path: "/assets",
|
|
120
|
-
tags: ["Assets"],
|
|
121
|
-
summary: "List assets",
|
|
122
|
-
successDescription: "Every asset in the space, with file metadata and usage counts."
|
|
123
|
-
}).output(z.array(AssetVOSchema)),
|
|
124
|
-
get: oc.route({
|
|
125
|
-
method: "GET",
|
|
126
|
-
path: "/assets/{assetId}",
|
|
127
|
-
tags: ["Assets"],
|
|
128
|
-
summary: "Get asset detail",
|
|
129
|
-
successDescription: "Asset metadata plus every place it is referenced (where-used)."
|
|
130
|
-
}).input(z.object({ assetId: z.string() })).output(AssetDetailVOSchema),
|
|
131
|
-
updateMetadata: oc.route({
|
|
132
|
-
method: "PATCH",
|
|
133
|
-
path: "/assets/{assetId}/metadata",
|
|
134
|
-
tags: ["Assets"],
|
|
135
|
-
summary: "Update asset metadata",
|
|
136
|
-
successDescription: "Updated AI-readable metadata for a file, such as summary, extracted text, tags, source URL, or schema-specific hints."
|
|
137
|
-
}).input(UpdateAssetMetadataInputSchema).output(AssetDetailVOSchema),
|
|
138
|
-
delete: oc.route({
|
|
139
|
-
method: "DELETE",
|
|
140
|
-
path: "/assets/{assetId}",
|
|
141
|
-
tags: ["Assets"],
|
|
142
|
-
summary: "Delete asset",
|
|
143
|
-
successDescription: "Removed the asset and, if no other row references its bytes, the stored object. Refused while the asset is still referenced (where-used)."
|
|
144
|
-
}).input(z.object({ assetId: z.string() })).output(z.object({ deleted: z.boolean() }))
|
|
145
|
-
};
|
|
146
7
|
var i18n = {
|
|
147
8
|
locales: ["en", "zh-CN", "zh-TW", "ja", "ko", "de", "fr", "es", "pt"]};
|
|
148
9
|
var LocaleSchema = z.enum(i18n.locales);
|
|
@@ -263,7 +124,12 @@ var createBaseInputSchema = z.object({
|
|
|
263
124
|
required: z.boolean().default(false),
|
|
264
125
|
options: fieldOptionsSchema.optional().default({})
|
|
265
126
|
})
|
|
266
|
-
).default([])
|
|
127
|
+
).default([]),
|
|
128
|
+
// Review-first by default: without `autoMerge: true`, this proposes the Base
|
|
129
|
+
// as a pending ChangeRequest (status "in_review") instead of creating it
|
|
130
|
+
// immediately. Pass `autoMerge: true` only for callers that don't need human
|
|
131
|
+
// review (seed/migration scripts, an explicit no-review agent task).
|
|
132
|
+
autoMerge: z.boolean().optional().default(false)
|
|
267
133
|
});
|
|
268
134
|
var createBaseFieldInputSchema = z.object({
|
|
269
135
|
name: fieldNameSchema,
|
|
@@ -330,6 +196,43 @@ var restoreFieldChangeRequestInputSchema = z.object({
|
|
|
330
196
|
submittedBy: z.string().optional().default("local-editor")
|
|
331
197
|
});
|
|
332
198
|
|
|
199
|
+
// ../../packages/busabase-contract/src/domains/filetree/definition.ts
|
|
200
|
+
var fileTreeOperations = (type) => [
|
|
201
|
+
{
|
|
202
|
+
kind: `${type}_file_create`,
|
|
203
|
+
label: "Create file",
|
|
204
|
+
tone: "border-emerald-200 bg-emerald-50 text-emerald-800"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
kind: `${type}_file_update`,
|
|
208
|
+
label: "Update file",
|
|
209
|
+
tone: "border-blue-200 bg-blue-50 text-blue-800"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
kind: `${type}_file_delete`,
|
|
213
|
+
label: "Delete file",
|
|
214
|
+
tone: "border-rose-200 bg-rose-50 text-rose-800"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
kind: `${type}_metadata_update`,
|
|
218
|
+
label: `Update ${type}`,
|
|
219
|
+
tone: "border-violet-200 bg-violet-50 text-violet-800"
|
|
220
|
+
}
|
|
221
|
+
];
|
|
222
|
+
var makeFileTreeNodeType = (config) => ({
|
|
223
|
+
type: config.type,
|
|
224
|
+
label: config.label,
|
|
225
|
+
icon: config.icon,
|
|
226
|
+
capabilities: { hasDetail: true, creatable: true },
|
|
227
|
+
operations: fileTreeOperations(config.type)
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// ../../packages/busabase-contract/src/domains/airapp/definition.ts
|
|
231
|
+
var airappNodeType = makeFileTreeNodeType({
|
|
232
|
+
type: "airapp",
|
|
233
|
+
label: "AirApp",
|
|
234
|
+
icon: "app-window"});
|
|
235
|
+
|
|
333
236
|
// ../../packages/busabase-contract/src/domains/base/definition.ts
|
|
334
237
|
var baseNodeType = {
|
|
335
238
|
type: "base",
|
|
@@ -424,37 +327,6 @@ var docNodeType = {
|
|
|
424
327
|
]
|
|
425
328
|
};
|
|
426
329
|
|
|
427
|
-
// ../../packages/busabase-contract/src/domains/filetree/definition.ts
|
|
428
|
-
var fileTreeOperations = (type) => [
|
|
429
|
-
{
|
|
430
|
-
kind: `${type}_file_create`,
|
|
431
|
-
label: "Create file",
|
|
432
|
-
tone: "border-emerald-200 bg-emerald-50 text-emerald-800"
|
|
433
|
-
},
|
|
434
|
-
{
|
|
435
|
-
kind: `${type}_file_update`,
|
|
436
|
-
label: "Update file",
|
|
437
|
-
tone: "border-blue-200 bg-blue-50 text-blue-800"
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
kind: `${type}_file_delete`,
|
|
441
|
-
label: "Delete file",
|
|
442
|
-
tone: "border-rose-200 bg-rose-50 text-rose-800"
|
|
443
|
-
},
|
|
444
|
-
{
|
|
445
|
-
kind: `${type}_metadata_update`,
|
|
446
|
-
label: `Update ${type}`,
|
|
447
|
-
tone: "border-violet-200 bg-violet-50 text-violet-800"
|
|
448
|
-
}
|
|
449
|
-
];
|
|
450
|
-
var makeFileTreeNodeType = (config) => ({
|
|
451
|
-
type: config.type,
|
|
452
|
-
label: config.label,
|
|
453
|
-
icon: config.icon,
|
|
454
|
-
capabilities: { hasDetail: true, creatable: true },
|
|
455
|
-
operations: fileTreeOperations(config.type)
|
|
456
|
-
});
|
|
457
|
-
|
|
458
330
|
// ../../packages/busabase-contract/src/domains/drive/definition.ts
|
|
459
331
|
var driveNodeType = makeFileTreeNodeType({
|
|
460
332
|
type: "drive",
|
|
@@ -510,6 +382,7 @@ var BUILTIN_NODE_TYPES = [
|
|
|
510
382
|
baseNodeType,
|
|
511
383
|
skillNodeType,
|
|
512
384
|
driveNodeType,
|
|
385
|
+
airappNodeType,
|
|
513
386
|
fileNodeType,
|
|
514
387
|
docNodeType
|
|
515
388
|
];
|
|
@@ -695,7 +568,14 @@ var liveEventSchema = z.object({
|
|
|
695
568
|
"change_request.updated",
|
|
696
569
|
"change_request.deleted",
|
|
697
570
|
"change_request.reviewed",
|
|
698
|
-
"change_request.merged"
|
|
571
|
+
"change_request.merged",
|
|
572
|
+
// Fired only when a CONTENT change request freshly enters human review
|
|
573
|
+
// (record_* ops created via record-ops.ts) — never for structural ops
|
|
574
|
+
// that auto-merge instantly (those still fire "change_request.created"
|
|
575
|
+
// via the audit funnel, but nothing needs reviewing). Consumed by
|
|
576
|
+
// `use-live-sync.ts` to pop a desktop Notification, and by
|
|
577
|
+
// busabase-cloud's host hook to persist an inbox notification row.
|
|
578
|
+
"change_request.pending_review"
|
|
699
579
|
]),
|
|
700
580
|
spaceId: z.string(),
|
|
701
581
|
actorId: z.string(),
|
|
@@ -723,8 +603,11 @@ var auditActionSchema = z.enum([
|
|
|
723
603
|
"file.created",
|
|
724
604
|
"skill.created",
|
|
725
605
|
"drive.created",
|
|
606
|
+
"airapp.created",
|
|
726
607
|
"asset.deleted",
|
|
727
608
|
"asset.metadata_updated",
|
|
609
|
+
"asset.text_written",
|
|
610
|
+
"asset.text_marked_none",
|
|
728
611
|
"node.purged"
|
|
729
612
|
]);
|
|
730
613
|
var auditEventSchema = z.object({
|
|
@@ -810,6 +693,13 @@ var createNodeChangeRequestInputSchema = z.object({
|
|
|
810
693
|
),
|
|
811
694
|
operations: z.array(nodeOperationInputSchema).min(1)
|
|
812
695
|
});
|
|
696
|
+
var moveNodeInputSchema = z.object({
|
|
697
|
+
nodeId: z.string(),
|
|
698
|
+
parentNodeId: z.string().optional().describe("New parent folder node id. Omit to keep the current parent and only reorder."),
|
|
699
|
+
position: z.number().int().optional().describe("New position among the target parent's children."),
|
|
700
|
+
message: z.string().optional().describe("Reviewer-facing Change Request message."),
|
|
701
|
+
submittedBy: z.string().optional()
|
|
702
|
+
});
|
|
813
703
|
var createDeleteChangeRequestInputSchema = z.object({
|
|
814
704
|
message: z.string().optional().default("Delete record").describe(
|
|
815
705
|
'Explanation shown to the human reviewer. Say what is being removed and why, e.g. "Archive duplicate contact \u2014 merged into Acme Corp".'
|
|
@@ -898,6 +788,477 @@ var authInfoSchema = z.object({
|
|
|
898
788
|
*/
|
|
899
789
|
spaces: z.array(authSpaceSchema)
|
|
900
790
|
});
|
|
791
|
+
|
|
792
|
+
// ../../packages/busabase-contract/src/domains/filetree/contract.ts
|
|
793
|
+
var fileTreeFileSchema = z.object({
|
|
794
|
+
path: z.string(),
|
|
795
|
+
name: z.string(),
|
|
796
|
+
size: z.number(),
|
|
797
|
+
updatedAt: z.string().nullable(),
|
|
798
|
+
mimeType: z.string().nullable(),
|
|
799
|
+
assetId: z.string(),
|
|
800
|
+
displayName: z.string().nullable()
|
|
801
|
+
});
|
|
802
|
+
var assetFileInputSchema = z.object({
|
|
803
|
+
path: z.string().min(1),
|
|
804
|
+
assetId: z.string().min(1),
|
|
805
|
+
displayName: z.string().optional(),
|
|
806
|
+
mimeType: z.string().optional()
|
|
807
|
+
}).strict();
|
|
808
|
+
var textFileInputSchema = z.object({
|
|
809
|
+
path: z.string().min(1),
|
|
810
|
+
content: z.string().default(""),
|
|
811
|
+
mimeType: z.string().optional()
|
|
812
|
+
}).strict();
|
|
813
|
+
var assetFileOperationInputSchema = z.object({
|
|
814
|
+
kind: z.enum(["create", "update"]),
|
|
815
|
+
path: z.string().min(1),
|
|
816
|
+
assetId: z.string().min(1),
|
|
817
|
+
displayName: z.string().optional(),
|
|
818
|
+
mimeType: z.string().optional(),
|
|
819
|
+
baseContentHash: z.string().optional()
|
|
820
|
+
}).strict();
|
|
821
|
+
var textFileOperationInputSchema = z.object({
|
|
822
|
+
kind: z.enum(["create", "update"]),
|
|
823
|
+
path: z.string().min(1),
|
|
824
|
+
content: z.string(),
|
|
825
|
+
mimeType: z.string().optional(),
|
|
826
|
+
baseContentHash: z.string().optional()
|
|
827
|
+
}).strict();
|
|
828
|
+
var fileTreeNodeSchema = z.object({
|
|
829
|
+
node: nodeSchema,
|
|
830
|
+
entryFile: z.string(),
|
|
831
|
+
visibility: z.enum(["private", "workspace", "public"]),
|
|
832
|
+
version: z.string(),
|
|
833
|
+
files: z.array(fileTreeFileSchema)
|
|
834
|
+
});
|
|
835
|
+
var createFileTreeInputSchema = z.object({
|
|
836
|
+
parentNodeId: z.string().optional(),
|
|
837
|
+
slug: z.string().min(1).regex(/^[a-z0-9-]+$/),
|
|
838
|
+
name: z.string().min(1),
|
|
839
|
+
description: z.string().optional().default(""),
|
|
840
|
+
visibility: z.enum(["private", "workspace", "public"]).optional().default("private"),
|
|
841
|
+
version: z.string().optional().default("0.1.0"),
|
|
842
|
+
files: z.array(z.union([assetFileInputSchema, textFileInputSchema])).optional().default([]),
|
|
843
|
+
// Review-first by default: without `autoMerge: true`, this proposes the node
|
|
844
|
+
// as a pending ChangeRequest (status "in_review") instead of creating it
|
|
845
|
+
// immediately. Pass `autoMerge: true` only for callers that don't need human
|
|
846
|
+
// review (seed/migration scripts, an explicit no-review agent task).
|
|
847
|
+
autoMerge: z.boolean().optional().default(false),
|
|
848
|
+
// "merge" (default): `files` is layered on top of the config's default seed
|
|
849
|
+
// files by path — a caller supplying just a couple of extra files (e.g. a
|
|
850
|
+
// Skill's own reference doc) still gets the default scaffold (SKILL.md,
|
|
851
|
+
// skill.json, ...) for any path they didn't provide themselves. "replace":
|
|
852
|
+
// `files` replaces the defaults entirely — for a caller handing over a
|
|
853
|
+
// complete, different-shaped project (e.g. an AirApp seeded with a Vite
|
|
854
|
+
// project instead of the default Hono template) who does NOT want leftover
|
|
855
|
+
// default files with unrelated content mixed in.
|
|
856
|
+
mergeMode: z.enum(["merge", "replace"]).optional().default("merge")
|
|
857
|
+
});
|
|
858
|
+
var fileTreeFileOperationInputSchema = z.union([
|
|
859
|
+
assetFileOperationInputSchema,
|
|
860
|
+
textFileOperationInputSchema,
|
|
861
|
+
z.object({
|
|
862
|
+
kind: z.literal("delete"),
|
|
863
|
+
path: z.string().min(1),
|
|
864
|
+
baseContentHash: z.string().optional()
|
|
865
|
+
}).strict(),
|
|
866
|
+
z.object({
|
|
867
|
+
kind: z.literal("metadata_update"),
|
|
868
|
+
metadata: z.object({
|
|
869
|
+
entryFile: z.string().optional(),
|
|
870
|
+
visibility: z.enum(["private", "workspace", "public"]).optional(),
|
|
871
|
+
version: z.string().optional()
|
|
872
|
+
}).default({})
|
|
873
|
+
}).strict()
|
|
874
|
+
]);
|
|
875
|
+
var createFileTreeChangeRequestInputSchema = z.object({
|
|
876
|
+
message: z.string().optional().default("Update file tree").describe(
|
|
877
|
+
'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Rewrite README.md quickstart for the new auth flow".'
|
|
878
|
+
),
|
|
879
|
+
submittedBy: z.string().optional().default("local-producer"),
|
|
880
|
+
operations: z.array(fileTreeFileOperationInputSchema).min(1)
|
|
881
|
+
});
|
|
882
|
+
var makeFileTreeContract = (routeBase, tag) => {
|
|
883
|
+
const label = tag.endsWith("s") ? tag.slice(0, -1) : tag;
|
|
884
|
+
const basePath = `/${routeBase}`;
|
|
885
|
+
return {
|
|
886
|
+
list: oc.route({
|
|
887
|
+
method: "GET",
|
|
888
|
+
path: basePath,
|
|
889
|
+
tags: [tag],
|
|
890
|
+
summary: `List ${label} nodes`,
|
|
891
|
+
successDescription: `${label} nodes with their Asset-backed file trees.`
|
|
892
|
+
}).output(z.array(fileTreeNodeSchema)),
|
|
893
|
+
create: oc.route({
|
|
894
|
+
method: "POST",
|
|
895
|
+
path: basePath,
|
|
896
|
+
tags: [tag],
|
|
897
|
+
summary: `Create ${label} node`,
|
|
898
|
+
successDescription: `Review-first by default: a pending ChangeRequest proposing the ${label} node. Returns the materialized ${label} node instead when \`autoMerge: true\` is passed.`
|
|
899
|
+
}).input(createFileTreeInputSchema).output(z.union([fileTreeNodeSchema, changeRequestSchema])),
|
|
900
|
+
get: oc.route({
|
|
901
|
+
method: "GET",
|
|
902
|
+
path: `${basePath}/{nodeId}`,
|
|
903
|
+
tags: [tag],
|
|
904
|
+
summary: `Get ${label} node`,
|
|
905
|
+
successDescription: `${label} node detail and file tree.`
|
|
906
|
+
}).input(z.object({ nodeId: z.string() })).output(fileTreeNodeSchema),
|
|
907
|
+
listFiles: oc.route({
|
|
908
|
+
method: "GET",
|
|
909
|
+
path: `${basePath}/{nodeId}/files`,
|
|
910
|
+
tags: [tag],
|
|
911
|
+
summary: `List ${label} files`,
|
|
912
|
+
successDescription: `Asset-backed files mounted under the ${label} node.`
|
|
913
|
+
}).input(z.object({ nodeId: z.string() })).output(z.array(fileTreeFileSchema)),
|
|
914
|
+
readFile: oc.route({
|
|
915
|
+
method: "GET",
|
|
916
|
+
path: `${basePath}/{nodeId}/files/{+filePath}`,
|
|
917
|
+
tags: [tag],
|
|
918
|
+
summary: `Read ${label} file`,
|
|
919
|
+
successDescription: `${label} file content and content hash.`
|
|
920
|
+
}).input(z.object({ nodeId: z.string(), filePath: z.string() })).output(
|
|
921
|
+
z.object({
|
|
922
|
+
nodeId: z.string(),
|
|
923
|
+
path: z.string(),
|
|
924
|
+
encoding: z.enum(["utf8", "url"]),
|
|
925
|
+
content: z.string(),
|
|
926
|
+
mimeType: z.string(),
|
|
927
|
+
assetId: z.string(),
|
|
928
|
+
displayName: z.string().nullable(),
|
|
929
|
+
assetUrl: z.string().nullable(),
|
|
930
|
+
contentHash: z.string()
|
|
931
|
+
})
|
|
932
|
+
),
|
|
933
|
+
createChangeRequest: oc.route({
|
|
934
|
+
method: "POST",
|
|
935
|
+
path: `${basePath}/{nodeId}/change-requests`,
|
|
936
|
+
tags: [tag, "Change Requests"],
|
|
937
|
+
summary: `Create ${label} change request`,
|
|
938
|
+
successDescription: `Created file-tree change request for a ${label} node.`
|
|
939
|
+
}).input(createFileTreeChangeRequestInputSchema.extend({ nodeId: z.string() })).output(changeRequestSchema)
|
|
940
|
+
};
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
// ../../packages/busabase-contract/src/domains/airapp/contract.ts
|
|
944
|
+
var airappContract = makeFileTreeContract("airapps", "AirApps");
|
|
945
|
+
var AttachmentMetadataSchema = z.record(z.string(), z.unknown());
|
|
946
|
+
z.object({
|
|
947
|
+
id: z.string(),
|
|
948
|
+
url: z.string(),
|
|
949
|
+
fileName: z.string(),
|
|
950
|
+
mimeType: z.string(),
|
|
951
|
+
size: z.number().int().nonnegative()
|
|
952
|
+
});
|
|
953
|
+
var RequestUploadUrlInputSchema = z.object({
|
|
954
|
+
fileName: z.string().min(1).max(255),
|
|
955
|
+
mimeType: z.string().min(1),
|
|
956
|
+
sizeBytes: z.number().int().positive(),
|
|
957
|
+
spaceId: z.string().optional(),
|
|
958
|
+
context: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(100).optional(),
|
|
959
|
+
/** Content fingerprint (e.g. "sha256:<hex>") for dedup; computed client-side. */
|
|
960
|
+
contentHash: z.string().max(80).optional()
|
|
961
|
+
});
|
|
962
|
+
var ConfirmUploadInputSchema = z.object({
|
|
963
|
+
storageKey: z.string().min(1),
|
|
964
|
+
fileName: z.string().min(1).max(255),
|
|
965
|
+
mimeType: z.string().min(1),
|
|
966
|
+
sizeBytes: z.number().int().positive(),
|
|
967
|
+
spaceId: z.string().optional(),
|
|
968
|
+
context: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(100).optional(),
|
|
969
|
+
metadata: AttachmentMetadataSchema.optional(),
|
|
970
|
+
/** Content fingerprint (e.g. "sha256:<hex>") persisted for dedup. */
|
|
971
|
+
contentHash: z.string().max(80).optional()
|
|
972
|
+
});
|
|
973
|
+
var RequestUploadUrlVOSchema = z.object({
|
|
974
|
+
uploadUrl: z.string(),
|
|
975
|
+
storageKey: z.string(),
|
|
976
|
+
publicUrl: z.string(),
|
|
977
|
+
expiresIn: z.number(),
|
|
978
|
+
/**
|
|
979
|
+
* True when an identical file (same contentHash, same scope) already exists.
|
|
980
|
+
* The client should SKIP the byte upload and the confirm step, and use
|
|
981
|
+
* `attachmentId`/`publicUrl` directly. `uploadUrl` is empty in this case.
|
|
982
|
+
*/
|
|
983
|
+
duplicate: z.boolean().optional(),
|
|
984
|
+
/** Existing attachment id — present only when `duplicate` is true. */
|
|
985
|
+
attachmentId: z.string().optional(),
|
|
986
|
+
/** Host-specific logical asset id, when the app maps uploads into an Asset library. */
|
|
987
|
+
assetId: z.string().optional()
|
|
988
|
+
});
|
|
989
|
+
var ConfirmUploadVOSchema = z.object({
|
|
990
|
+
success: z.boolean(),
|
|
991
|
+
attachmentId: z.string(),
|
|
992
|
+
/** Host-specific logical asset id, when the app maps uploads into an Asset library. */
|
|
993
|
+
assetId: z.string().optional(),
|
|
994
|
+
storageKey: z.string(),
|
|
995
|
+
publicUrl: z.string()
|
|
996
|
+
});
|
|
997
|
+
var AssetTextStatusSchema = z.enum(["missing", "present", "none", "stale"]);
|
|
998
|
+
var AssetVOSchema = z.object({
|
|
999
|
+
id: z.string(),
|
|
1000
|
+
attachmentId: z.string(),
|
|
1001
|
+
name: z.string(),
|
|
1002
|
+
contentKind: z.enum(["text", "binary"]),
|
|
1003
|
+
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
1004
|
+
fileName: z.string(),
|
|
1005
|
+
mimeType: z.string(),
|
|
1006
|
+
size: z.number().int().nonnegative(),
|
|
1007
|
+
url: z.string(),
|
|
1008
|
+
contentHash: z.string().nullable(),
|
|
1009
|
+
/** How many places reference this asset (Base records + Doc bodies). */
|
|
1010
|
+
usageCount: z.number().int().nonnegative(),
|
|
1011
|
+
/** Drive Grep Retrieval text-slot status — see {@link AssetTextStatusSchema}. */
|
|
1012
|
+
textStatus: AssetTextStatusSchema,
|
|
1013
|
+
createdAt: z.string()
|
|
1014
|
+
});
|
|
1015
|
+
var AssetUsageVOSchema = z.object({
|
|
1016
|
+
ownerType: z.enum(["drive", "skill", "airapp", "base", "doc", "file_node"]),
|
|
1017
|
+
nodeId: z.string(),
|
|
1018
|
+
nodeName: z.string(),
|
|
1019
|
+
nodeType: z.string(),
|
|
1020
|
+
/** Node slug, so the UI can link to the owning Base/Doc (`/{type}/{slug}`). */
|
|
1021
|
+
nodeSlug: z.string(),
|
|
1022
|
+
/** Drive/Skill mounted path, or null when the usage is not path-based. */
|
|
1023
|
+
path: z.string().nullable(),
|
|
1024
|
+
/** Base record id, or null for whole-node usages (e.g. a Doc body). */
|
|
1025
|
+
recordId: z.string().nullable(),
|
|
1026
|
+
/** Attachment field slug, or null for whole-node usages. */
|
|
1027
|
+
fieldSlug: z.string().nullable(),
|
|
1028
|
+
/** Doc block id, or null when the usage is not block-based. */
|
|
1029
|
+
blockId: z.string().nullable(),
|
|
1030
|
+
createdAt: z.string()
|
|
1031
|
+
});
|
|
1032
|
+
var AssetDetailVOSchema = z.object({
|
|
1033
|
+
asset: AssetVOSchema,
|
|
1034
|
+
usages: z.array(AssetUsageVOSchema)
|
|
1035
|
+
});
|
|
1036
|
+
var PutTextInputSchema = z.object({
|
|
1037
|
+
assetId: z.string(),
|
|
1038
|
+
/** Inline text body, ≤ 1 MB. For larger text, use `createTextUploadUrl` + bind by `storageKey`. */
|
|
1039
|
+
text: z.string().optional(),
|
|
1040
|
+
/** Bind a presigned-uploaded text object (a temp `asset-texts/pending/*.txt` key). */
|
|
1041
|
+
storageKey: z.string().optional(),
|
|
1042
|
+
/**
|
|
1043
|
+
* Claimed content hash for the `storageKey` bind path (`sha256:<hex>`, echoing
|
|
1044
|
+
* `createTextUploadUrl`'s input like `open-domains/attachments`' confirm step).
|
|
1045
|
+
* The server always computes the ACTUAL hash from the bytes during the
|
|
1046
|
+
* confirm scan and rejects a mismatch (hash-poisoning defense) — this field
|
|
1047
|
+
* is only an optional early-mismatch check, never trusted for addressing.
|
|
1048
|
+
*/
|
|
1049
|
+
contentHash: z.string().optional(),
|
|
1050
|
+
/** Mark as having no extractable text (e.g. a scanned, image-only PDF). */
|
|
1051
|
+
none: z.boolean().optional()
|
|
1052
|
+
});
|
|
1053
|
+
var AssetTextVOSchema = z.object({
|
|
1054
|
+
assetId: z.string(),
|
|
1055
|
+
textStatus: AssetTextStatusSchema,
|
|
1056
|
+
lineCount: z.number().int().nonnegative(),
|
|
1057
|
+
charCount: z.number().int().nonnegative(),
|
|
1058
|
+
byteCount: z.number().int().nonnegative()
|
|
1059
|
+
});
|
|
1060
|
+
var CreateTextUploadUrlInputSchema = z.object({
|
|
1061
|
+
assetId: z.string(),
|
|
1062
|
+
sizeBytes: z.number().int().positive(),
|
|
1063
|
+
/** Optional claim, mirrors `RequestUploadUrlDTO.contentHash` (never trusted for addressing). */
|
|
1064
|
+
contentHash: z.string().optional()
|
|
1065
|
+
});
|
|
1066
|
+
var CreateTextUploadUrlVOSchema = z.object({
|
|
1067
|
+
uploadUrl: z.string(),
|
|
1068
|
+
storageKey: z.string(),
|
|
1069
|
+
expiresIn: z.number().int().nonnegative()
|
|
1070
|
+
});
|
|
1071
|
+
var GrepScopeSchema = z.object({
|
|
1072
|
+
assetIds: z.array(z.string()).optional(),
|
|
1073
|
+
/** Drive/Skill mounted path prefix (matches `busabase_asset_usages.path`). */
|
|
1074
|
+
drivePath: z.string().optional(),
|
|
1075
|
+
mimeTypes: z.array(z.string()).optional()
|
|
1076
|
+
});
|
|
1077
|
+
var GREP_DEFAULT_MAX_MATCHES = 100;
|
|
1078
|
+
var GREP_HARD_MAX_MATCHES = 1e3;
|
|
1079
|
+
var GREP_DEFAULT_CONTEXT_LINES = 0;
|
|
1080
|
+
var GREP_MAX_CONTEXT_LINES = 10;
|
|
1081
|
+
var GrepInputSchema = z.object({
|
|
1082
|
+
pattern: z.string().min(1),
|
|
1083
|
+
/** JS RegExp flags, e.g. `"i"` for case-insensitive. `g`/`y` are ignored (grep always scans every match per line). */
|
|
1084
|
+
flags: z.string().optional().default(""),
|
|
1085
|
+
scope: GrepScopeSchema.optional(),
|
|
1086
|
+
maxMatches: z.coerce.number().int().min(1).max(GREP_HARD_MAX_MATCHES).optional().default(GREP_DEFAULT_MAX_MATCHES),
|
|
1087
|
+
contextLines: z.coerce.number().int().min(0).max(GREP_MAX_CONTEXT_LINES).optional().default(GREP_DEFAULT_CONTEXT_LINES)
|
|
1088
|
+
});
|
|
1089
|
+
var GrepMatchVOSchema = z.object({
|
|
1090
|
+
assetId: z.string(),
|
|
1091
|
+
fileName: z.string(),
|
|
1092
|
+
/** Drive/Skill mounted path, or "" when the asset isn't path-mounted (e.g. a File node). */
|
|
1093
|
+
drivePath: z.string(),
|
|
1094
|
+
line: z.number().int().positive(),
|
|
1095
|
+
/** 1-based character column (not byte offset) of the match start within the line. */
|
|
1096
|
+
column: z.number().int().positive(),
|
|
1097
|
+
/** The matching line, truncated if it exceeds the long-line guard. */
|
|
1098
|
+
text: z.string(),
|
|
1099
|
+
before: z.array(z.string()),
|
|
1100
|
+
after: z.array(z.string())
|
|
1101
|
+
});
|
|
1102
|
+
var GrepResultVOSchema = z.object({
|
|
1103
|
+
matches: z.array(GrepMatchVOSchema),
|
|
1104
|
+
filesScanned: z.number().int().nonnegative(),
|
|
1105
|
+
/** Asset ids in scope with no text yet (contentKind text-or-writable-binary, no row). */
|
|
1106
|
+
missing: z.array(z.string()),
|
|
1107
|
+
/** Asset ids in scope whose derived text is stale (source replaced since it was written). */
|
|
1108
|
+
stale: z.array(z.string()),
|
|
1109
|
+
/** Count of assets in scope explicitly marked `none` (no extractable text). */
|
|
1110
|
+
unsearchable: z.number().int().nonnegative(),
|
|
1111
|
+
/**
|
|
1112
|
+
* Asset ids whose scan was attempted but failed (storage error, corrupt
|
|
1113
|
+
* cache file, object deleted mid-flight) — NOT counted in `filesScanned`.
|
|
1114
|
+
* Honest coverage: these were not actually searched, so a caller must not
|
|
1115
|
+
* treat their absence from `matches` as a clean "no match".
|
|
1116
|
+
*/
|
|
1117
|
+
errored: z.array(z.string()),
|
|
1118
|
+
/**
|
|
1119
|
+
* Count of in-scope, present-and-searchable assets the scan never even
|
|
1120
|
+
* reached because the deadline or `maxMatches` budget ran out first. Only
|
|
1121
|
+
* nonzero when `truncated` is true.
|
|
1122
|
+
*/
|
|
1123
|
+
notReached: z.number().int().nonnegative(),
|
|
1124
|
+
truncated: z.boolean()
|
|
1125
|
+
});
|
|
1126
|
+
var ReadTextLinesInputSchema = z.object({
|
|
1127
|
+
assetId: z.string(),
|
|
1128
|
+
startLine: z.coerce.number().int().min(1),
|
|
1129
|
+
endLine: z.coerce.number().int().min(1)
|
|
1130
|
+
});
|
|
1131
|
+
var ReadLinesVOSchema = z.object({
|
|
1132
|
+
lines: z.array(z.string()),
|
|
1133
|
+
startLine: z.number().int().positive(),
|
|
1134
|
+
endLine: z.number().int().positive(),
|
|
1135
|
+
totalLines: z.number().int().nonnegative(),
|
|
1136
|
+
truncated: z.boolean()
|
|
1137
|
+
});
|
|
1138
|
+
var AssetContentEditSchema = z.object({
|
|
1139
|
+
oldString: z.string().min(1),
|
|
1140
|
+
newString: z.string(),
|
|
1141
|
+
/** Replace every occurrence instead of requiring a single unique match. */
|
|
1142
|
+
replaceAll: z.boolean().optional().default(false)
|
|
1143
|
+
}).strict();
|
|
1144
|
+
var EditAssetContentInputSchema = z.object({
|
|
1145
|
+
assetId: z.string(),
|
|
1146
|
+
edits: z.array(AssetContentEditSchema).min(1),
|
|
1147
|
+
message: z.string().optional().default("Edit file content").describe(
|
|
1148
|
+
'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Fix typo in setup instructions".'
|
|
1149
|
+
),
|
|
1150
|
+
submittedBy: z.string().optional().default("agent")
|
|
1151
|
+
});
|
|
1152
|
+
var AssetDownloadInputSchema = z.object({ assetId: z.string() });
|
|
1153
|
+
var AssetDownloadVOSchema = z.object({
|
|
1154
|
+
assetId: z.string(),
|
|
1155
|
+
downloadUrl: z.string(),
|
|
1156
|
+
fileName: z.string(),
|
|
1157
|
+
mimeType: z.string(),
|
|
1158
|
+
size: z.number().int().nonnegative(),
|
|
1159
|
+
contentHash: z.string().nullable()
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
// ../../packages/busabase-contract/src/domains/assets/contract.ts
|
|
1163
|
+
var UpdateAssetMetadataInputSchema = z.object({
|
|
1164
|
+
assetId: z.string(),
|
|
1165
|
+
metadata: z.record(z.string(), z.unknown()),
|
|
1166
|
+
mode: z.enum(["merge", "replace"]).optional().default("merge")
|
|
1167
|
+
});
|
|
1168
|
+
var assetsContract = {
|
|
1169
|
+
createUploadUrl: oc.route({
|
|
1170
|
+
method: "POST",
|
|
1171
|
+
path: "/assets/upload-urls",
|
|
1172
|
+
tags: ["Assets"],
|
|
1173
|
+
summary: "Request asset upload URL",
|
|
1174
|
+
successDescription: "Presigned (or dev) upload URL plus the public URL and asset id when identical bytes are already in the library."
|
|
1175
|
+
}).input(RequestUploadUrlInputSchema).output(RequestUploadUrlVOSchema),
|
|
1176
|
+
confirm: oc.route({
|
|
1177
|
+
method: "POST",
|
|
1178
|
+
path: "/assets/confirmations",
|
|
1179
|
+
tags: ["Assets"],
|
|
1180
|
+
summary: "Confirm asset upload",
|
|
1181
|
+
successDescription: "Recorded the file and ensured its Busabase Asset library entry."
|
|
1182
|
+
}).input(ConfirmUploadInputSchema).output(ConfirmUploadVOSchema),
|
|
1183
|
+
list: oc.route({
|
|
1184
|
+
method: "GET",
|
|
1185
|
+
path: "/assets",
|
|
1186
|
+
tags: ["Assets"],
|
|
1187
|
+
summary: "List assets",
|
|
1188
|
+
successDescription: "Every asset in the space, with file metadata and usage counts."
|
|
1189
|
+
}).output(z.array(AssetVOSchema)),
|
|
1190
|
+
get: oc.route({
|
|
1191
|
+
method: "GET",
|
|
1192
|
+
path: "/assets/{assetId}",
|
|
1193
|
+
tags: ["Assets"],
|
|
1194
|
+
summary: "Get asset detail",
|
|
1195
|
+
successDescription: "Asset metadata plus every place it is referenced (where-used)."
|
|
1196
|
+
}).input(z.object({ assetId: z.string() })).output(AssetDetailVOSchema),
|
|
1197
|
+
updateMetadata: oc.route({
|
|
1198
|
+
method: "PATCH",
|
|
1199
|
+
path: "/assets/{assetId}/metadata",
|
|
1200
|
+
tags: ["Assets"],
|
|
1201
|
+
summary: "Update asset metadata",
|
|
1202
|
+
successDescription: "Updated AI-readable metadata for a file, such as summary, tags, source URL, or schema-specific hints. Large text does not live here \u2014 see putText / grep / readTextLines."
|
|
1203
|
+
}).input(UpdateAssetMetadataInputSchema).output(AssetDetailVOSchema),
|
|
1204
|
+
delete: oc.route({
|
|
1205
|
+
method: "DELETE",
|
|
1206
|
+
path: "/assets/{assetId}",
|
|
1207
|
+
tags: ["Assets"],
|
|
1208
|
+
summary: "Delete asset",
|
|
1209
|
+
successDescription: "Removed the asset and, if no other row references its bytes, the stored object. Refused while the asset is still referenced (where-used)."
|
|
1210
|
+
}).input(z.object({ assetId: z.string() })).output(z.object({ deleted: z.boolean() })),
|
|
1211
|
+
download: oc.route({
|
|
1212
|
+
method: "GET",
|
|
1213
|
+
path: "/assets/{assetId}/content",
|
|
1214
|
+
tags: ["Assets"],
|
|
1215
|
+
summary: "Get an asset's binary content download URL",
|
|
1216
|
+
successDescription: "A resolved, time-bounded download URL for the asset's raw bytes (local dev: the existing static /uploads route; cloud/S3: a presigned URL) plus its file metadata \u2014 see AssetDownloadVOSchema for why this returns a URL, not a raw-binary oRPC response."
|
|
1217
|
+
}).input(AssetDownloadInputSchema).output(AssetDownloadVOSchema),
|
|
1218
|
+
// ── Drive Grep Retrieval ─────────────────────────────────────────────────
|
|
1219
|
+
// Busabase stores, indexes, and searches text; it never generates it. Text
|
|
1220
|
+
// always arrives via putText — an agent's own extractor, or (future) an
|
|
1221
|
+
// Outgoing-Hook-triggered service — never a bundled parser/OCR library.
|
|
1222
|
+
putText: oc.route({
|
|
1223
|
+
method: "PUT",
|
|
1224
|
+
path: "/assets/{assetId}/text",
|
|
1225
|
+
tags: ["Assets"],
|
|
1226
|
+
summary: "Write (or mark none) an asset's text slot",
|
|
1227
|
+
successDescription: "Text slot updated: inline body (\u22641MB), or bound from a presigned upload (server-verified content hash, hash-poisoning-safe), or marked `none` for files with no extractable text. Direct write, audit-logged, not ChangeRequest-gated."
|
|
1228
|
+
}).input(PutTextInputSchema).output(AssetTextVOSchema),
|
|
1229
|
+
createTextUploadUrl: oc.route({
|
|
1230
|
+
method: "POST",
|
|
1231
|
+
path: "/assets/text/upload-urls",
|
|
1232
|
+
tags: ["Assets"],
|
|
1233
|
+
summary: "Request a presigned upload URL for large text",
|
|
1234
|
+
successDescription: "Presigned (or dev) upload URL for a temporary text object; PUT the bytes there, then call putText with the returned storageKey to bind, verify, and content-address it."
|
|
1235
|
+
}).input(CreateTextUploadUrlInputSchema).output(CreateTextUploadUrlVOSchema),
|
|
1236
|
+
grep: oc.route({
|
|
1237
|
+
method: "POST",
|
|
1238
|
+
path: "/assets/grep",
|
|
1239
|
+
tags: ["Assets"],
|
|
1240
|
+
summary: "Search every text-bearing asset in scope",
|
|
1241
|
+
successDescription: "Streaming regex/literal matches with real file + line + column numbers and context, across every asset with text \u2014 any size, no 256KB cap. Honest coverage: missing/stale/unsearchable/errored name assets that were not (fully or successfully) searched, notReached counts present assets the scan never got to, and truncated flags a capped response."
|
|
1242
|
+
}).input(GrepInputSchema).output(GrepResultVOSchema),
|
|
1243
|
+
readTextLines: oc.route({
|
|
1244
|
+
method: "GET",
|
|
1245
|
+
path: "/assets/{assetId}/text/lines",
|
|
1246
|
+
tags: ["Assets"],
|
|
1247
|
+
summary: "Read an exact line range from an asset's text",
|
|
1248
|
+
successDescription: "Lines [startLine, endLine] (range capped at 2000 lines / ~2MB response) read via a storage byte-range request \u2014 the server never loads the whole object, even for a multi-GB file."
|
|
1249
|
+
}).input(ReadTextLinesInputSchema).output(ReadLinesVOSchema),
|
|
1250
|
+
// ── editContent — edit an asset's REAL file content via ChangeRequest ──────
|
|
1251
|
+
// Unlike putText (derived/extracted text, direct write, disposable), this
|
|
1252
|
+
// edits the asset's actual mounted Drive/Skill file bytes and always goes
|
|
1253
|
+
// through the existing filetree ChangeRequest pipeline — never auto-merged.
|
|
1254
|
+
editContent: oc.route({
|
|
1255
|
+
method: "POST",
|
|
1256
|
+
path: "/assets/{assetId}/edit-content",
|
|
1257
|
+
tags: ["Assets", "Change Requests"],
|
|
1258
|
+
summary: "Edit an asset's file content via string-replace edits, as a ChangeRequest",
|
|
1259
|
+
successDescription: `Applied the string-replace edits (coding-agent Edit-tool semantics: unique-match or replaceAll) to the asset's current mounted Drive/Skill file content and proposed the result as a ChangeRequest (status "in_review") for human review. Reuses the existing filetree update-via-CR pipeline end to end, including baseContentHash optimistic-concurrency conflict protection at merge time. Requires the asset to be mounted in exactly one editable Drive/Skill location.`
|
|
1260
|
+
}).input(EditAssetContentInputSchema).output(changeRequestSchema)
|
|
1261
|
+
};
|
|
901
1262
|
var viewFilterOperatorSchema = z.enum([
|
|
902
1263
|
"contains",
|
|
903
1264
|
"equals",
|
|
@@ -1003,6 +1364,12 @@ var listRecordsResponseSchema = z.object({
|
|
|
1003
1364
|
records: z.array(recordSchema),
|
|
1004
1365
|
nextCursor: z.string().nullable()
|
|
1005
1366
|
});
|
|
1367
|
+
var listArchivedRecordsPagedInputSchema = z.object({
|
|
1368
|
+
baseId: z.string(),
|
|
1369
|
+
limit: z.coerce.number().int().min(1).max(100).optional().default(50),
|
|
1370
|
+
/** Opaque base64 cursor (createdAt-keyed) for keyset pagination. */
|
|
1371
|
+
cursor: z.string().optional()
|
|
1372
|
+
});
|
|
1006
1373
|
var countRecordsInputSchema = z.object({
|
|
1007
1374
|
baseId: z.string().optional()
|
|
1008
1375
|
}).optional().default({});
|
|
@@ -1101,13 +1468,20 @@ var baseContract = {
|
|
|
1101
1468
|
summary: "List archived records for a Base",
|
|
1102
1469
|
successDescription: "Records that have been archived (soft-deleted) from a Base."
|
|
1103
1470
|
}).input(z.object({ baseId: z.string() })).output(z.array(recordSchema)),
|
|
1471
|
+
listArchivedRecordsPaged: oc.route({
|
|
1472
|
+
method: "GET",
|
|
1473
|
+
path: "/bases/{baseId}/records/archived/paged",
|
|
1474
|
+
tags: ["Records"],
|
|
1475
|
+
summary: "List archived records for a Base with keyset pagination",
|
|
1476
|
+
successDescription: "A page of archived (soft-deleted) records plus an opaque nextCursor (null at the end)."
|
|
1477
|
+
}).input(listArchivedRecordsPagedInputSchema).output(listRecordsResponseSchema),
|
|
1104
1478
|
create: oc.route({
|
|
1105
1479
|
method: "POST",
|
|
1106
1480
|
path: "/bases",
|
|
1107
1481
|
tags: ["Bases"],
|
|
1108
1482
|
summary: "Create Base",
|
|
1109
|
-
successDescription: "
|
|
1110
|
-
}).input(createBaseInputSchema).output(baseSchema),
|
|
1483
|
+
successDescription: "Review-first by default: a pending ChangeRequest proposing the Base. Returns the materialized Base instead when `autoMerge: true` is passed."
|
|
1484
|
+
}).input(createBaseInputSchema).output(z.union([baseSchema, changeRequestSchema])),
|
|
1111
1485
|
createChangeRequest: oc.route({
|
|
1112
1486
|
method: "POST",
|
|
1113
1487
|
path: "/bases/{baseId}/change-requests",
|
|
@@ -1295,6 +1669,13 @@ var viewContract = {
|
|
|
1295
1669
|
successDescription: "Created change request that restores an archived View."
|
|
1296
1670
|
}).input(restoreViewInputSchema.extend({ viewId: z.string() })).output(changeRequestSchema)
|
|
1297
1671
|
};
|
|
1672
|
+
var ReadDocLinesInputSchema = z.object({
|
|
1673
|
+
nodeId: z.string(),
|
|
1674
|
+
startLine: z.coerce.number().int().min(1),
|
|
1675
|
+
endLine: z.coerce.number().int().min(1)
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
// ../../packages/busabase-contract/src/domains/doc/contract.ts
|
|
1298
1679
|
var docSchema = z.object({
|
|
1299
1680
|
node: nodeSchema,
|
|
1300
1681
|
storagePrefix: z.string(),
|
|
@@ -1305,7 +1686,12 @@ var createDocInputSchema = z.object({
|
|
|
1305
1686
|
slug: z.string().min(1).regex(/^[a-z0-9-]+$/),
|
|
1306
1687
|
name: z.string().min(1),
|
|
1307
1688
|
description: z.string().optional().default(""),
|
|
1308
|
-
body: z.string().optional().default("")
|
|
1689
|
+
body: z.string().optional().default(""),
|
|
1690
|
+
// Review-first by default: without `autoMerge: true`, this proposes the Doc
|
|
1691
|
+
// as a pending ChangeRequest (status "in_review") instead of creating it
|
|
1692
|
+
// immediately. Pass `autoMerge: true` only for callers that don't need human
|
|
1693
|
+
// review (seed/migration scripts, an explicit no-review agent task).
|
|
1694
|
+
autoMerge: z.boolean().optional().default(false)
|
|
1309
1695
|
});
|
|
1310
1696
|
var updateDocInputSchema = z.object({
|
|
1311
1697
|
body: z.string()
|
|
@@ -1330,8 +1716,8 @@ var docContract = {
|
|
|
1330
1716
|
path: "/docs",
|
|
1331
1717
|
tags: ["Docs"],
|
|
1332
1718
|
summary: "Create Doc node",
|
|
1333
|
-
successDescription: "
|
|
1334
|
-
}).input(createDocInputSchema).output(docSchema),
|
|
1719
|
+
successDescription: "Review-first by default: a pending ChangeRequest proposing the Doc. Returns the materialized Doc node instead when `autoMerge: true` is passed."
|
|
1720
|
+
}).input(createDocInputSchema).output(z.union([docSchema, changeRequestSchema])),
|
|
1335
1721
|
get: oc.route({
|
|
1336
1722
|
method: "GET",
|
|
1337
1723
|
path: "/docs/{nodeId}",
|
|
@@ -1339,6 +1725,13 @@ var docContract = {
|
|
|
1339
1725
|
summary: "Get Doc node",
|
|
1340
1726
|
successDescription: "Doc node detail and body."
|
|
1341
1727
|
}).input(z.object({ nodeId: z.string() })).output(docSchema),
|
|
1728
|
+
readLines: oc.route({
|
|
1729
|
+
method: "GET",
|
|
1730
|
+
path: "/docs/{nodeId}/lines",
|
|
1731
|
+
tags: ["Docs"],
|
|
1732
|
+
summary: "Read an exact line range from a Doc body",
|
|
1733
|
+
successDescription: "Lines [startLine, endLine] (range capped at 2000 lines / ~2MB response) sliced from the Doc's full body \u2014 Docs are KB-scale, so the whole body is read in memory; no byte-range/checkpoint machinery like assets.readTextLines uses for potentially multi-GB files. The Doc-domain follow-up to a Unified Grep match with `source: \"docs\"`, so an agent can read just the lines around a match instead of `get`'s entire body."
|
|
1734
|
+
}).input(ReadDocLinesInputSchema).output(ReadLinesVOSchema),
|
|
1342
1735
|
updateBody: oc.route({
|
|
1343
1736
|
method: "PUT",
|
|
1344
1737
|
path: "/docs/{nodeId}/body",
|
|
@@ -1354,144 +1747,100 @@ var docContract = {
|
|
|
1354
1747
|
successDescription: "Created a change request that proposes a new Doc body."
|
|
1355
1748
|
}).input(createDocChangeRequestInputSchema.extend({ nodeId: z.string() })).output(changeRequestSchema)
|
|
1356
1749
|
};
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1750
|
+
|
|
1751
|
+
// ../../packages/busabase-contract/src/domains/drive/contract.ts
|
|
1752
|
+
var driveContract = makeFileTreeContract("drives", "Drives");
|
|
1753
|
+
var DumpTableSchema = z.enum([
|
|
1754
|
+
"nodes",
|
|
1755
|
+
"bases",
|
|
1756
|
+
"baseFields",
|
|
1757
|
+
"views",
|
|
1758
|
+
"records",
|
|
1759
|
+
"fieldValues",
|
|
1760
|
+
"recordLinks",
|
|
1761
|
+
/** The physical bytes registry (open-domains/attachments) an Asset's `attachmentId` FKs into. */
|
|
1762
|
+
"attachments",
|
|
1763
|
+
"assets",
|
|
1764
|
+
"assetUsages",
|
|
1765
|
+
"assetTexts",
|
|
1766
|
+
"commits",
|
|
1767
|
+
"changeRequests",
|
|
1768
|
+
"operations",
|
|
1769
|
+
"comments",
|
|
1770
|
+
"reviews",
|
|
1771
|
+
"auditEvents"
|
|
1772
|
+
]);
|
|
1773
|
+
var ExportTablesInputSchema = z.object({
|
|
1774
|
+
table: DumpTableSchema,
|
|
1775
|
+
/** Opaque pagination cursor from a previous page's `nextCursor`; omit for the first page. */
|
|
1776
|
+
cursor: z.string().optional(),
|
|
1777
|
+
/** Page size, default 500, capped at 2000. */
|
|
1778
|
+
limit: z.number().int().positive().max(2e3).optional().default(500)
|
|
1365
1779
|
});
|
|
1366
|
-
var
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
displayName: z.string().optional(),
|
|
1370
|
-
mimeType: z.string().optional()
|
|
1371
|
-
}).strict();
|
|
1372
|
-
var textFileInputSchema = z.object({
|
|
1373
|
-
path: z.string().min(1),
|
|
1374
|
-
content: z.string().default(""),
|
|
1375
|
-
mimeType: z.string().optional()
|
|
1376
|
-
}).strict();
|
|
1377
|
-
var assetFileOperationInputSchema = z.object({
|
|
1378
|
-
kind: z.enum(["create", "update"]),
|
|
1379
|
-
path: z.string().min(1),
|
|
1380
|
-
assetId: z.string().min(1),
|
|
1381
|
-
displayName: z.string().optional(),
|
|
1382
|
-
mimeType: z.string().optional(),
|
|
1383
|
-
baseContentHash: z.string().optional()
|
|
1384
|
-
}).strict();
|
|
1385
|
-
var textFileOperationInputSchema = z.object({
|
|
1386
|
-
kind: z.enum(["create", "update"]),
|
|
1387
|
-
path: z.string().min(1),
|
|
1388
|
-
content: z.string(),
|
|
1389
|
-
mimeType: z.string().optional(),
|
|
1390
|
-
baseContentHash: z.string().optional()
|
|
1391
|
-
}).strict();
|
|
1392
|
-
var fileTreeNodeSchema = z.object({
|
|
1393
|
-
node: nodeSchema,
|
|
1394
|
-
entryFile: z.string(),
|
|
1395
|
-
visibility: z.enum(["private", "workspace", "public"]),
|
|
1396
|
-
version: z.string(),
|
|
1397
|
-
files: z.array(fileTreeFileSchema)
|
|
1780
|
+
var ExportTablesVOSchema = z.object({
|
|
1781
|
+
rows: z.array(z.record(z.string(), z.unknown())),
|
|
1782
|
+
nextCursor: z.string().nullable()
|
|
1398
1783
|
});
|
|
1399
|
-
var
|
|
1400
|
-
|
|
1401
|
-
slug: z.string().min(1).regex(/^[a-z0-9-]+$/),
|
|
1402
|
-
name: z.string().min(1),
|
|
1403
|
-
description: z.string().optional().default(""),
|
|
1404
|
-
visibility: z.enum(["private", "workspace", "public"]).optional().default("private"),
|
|
1405
|
-
version: z.string().optional().default("0.1.0"),
|
|
1406
|
-
files: z.array(z.union([assetFileInputSchema, textFileInputSchema])).optional().default([])
|
|
1784
|
+
var ImportBeginVOSchema = z.object({
|
|
1785
|
+
sessionId: z.string()
|
|
1407
1786
|
});
|
|
1408
|
-
var
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
z.
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
var createFileTreeChangeRequestInputSchema = z.object({
|
|
1426
|
-
message: z.string().optional().default("Update file tree").describe(
|
|
1427
|
-
'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Rewrite README.md quickstart for the new auth flow".'
|
|
1428
|
-
),
|
|
1429
|
-
submittedBy: z.string().optional().default("local-producer"),
|
|
1430
|
-
operations: z.array(fileTreeFileOperationInputSchema).min(1)
|
|
1787
|
+
var ImportTablesInputSchema = z.object({
|
|
1788
|
+
sessionId: z.string(),
|
|
1789
|
+
table: z.union([DumpTableSchema, z.literal("docBodies"), z.literal("attachmentBlobs")]),
|
|
1790
|
+
rows: z.array(z.record(z.string(), z.unknown()))
|
|
1791
|
+
});
|
|
1792
|
+
var ImportTablesVOSchema = z.object({
|
|
1793
|
+
inserted: z.number().int().nonnegative()
|
|
1794
|
+
});
|
|
1795
|
+
var ImportSessionInputSchema = z.object({
|
|
1796
|
+
sessionId: z.string()
|
|
1797
|
+
});
|
|
1798
|
+
var ImportCommitVOSchema = z.object({
|
|
1799
|
+
ok: z.boolean(),
|
|
1800
|
+
warnings: z.array(z.string())
|
|
1801
|
+
});
|
|
1802
|
+
var ImportAbortVOSchema = z.object({
|
|
1803
|
+
ok: z.boolean()
|
|
1431
1804
|
});
|
|
1432
|
-
var makeFileTreeContract = (routeBase, tag) => {
|
|
1433
|
-
const label = tag.endsWith("s") ? tag.slice(0, -1) : tag;
|
|
1434
|
-
const basePath = `/${routeBase}`;
|
|
1435
|
-
return {
|
|
1436
|
-
list: oc.route({
|
|
1437
|
-
method: "GET",
|
|
1438
|
-
path: basePath,
|
|
1439
|
-
tags: [tag],
|
|
1440
|
-
summary: `List ${label} nodes`,
|
|
1441
|
-
successDescription: `${label} nodes with their Asset-backed file trees.`
|
|
1442
|
-
}).output(z.array(fileTreeNodeSchema)),
|
|
1443
|
-
create: oc.route({
|
|
1444
|
-
method: "POST",
|
|
1445
|
-
path: basePath,
|
|
1446
|
-
tags: [tag],
|
|
1447
|
-
summary: `Create ${label} node`,
|
|
1448
|
-
successDescription: `Created ${label} node and initialized file tree.`
|
|
1449
|
-
}).input(createFileTreeInputSchema).output(fileTreeNodeSchema),
|
|
1450
|
-
get: oc.route({
|
|
1451
|
-
method: "GET",
|
|
1452
|
-
path: `${basePath}/{nodeId}`,
|
|
1453
|
-
tags: [tag],
|
|
1454
|
-
summary: `Get ${label} node`,
|
|
1455
|
-
successDescription: `${label} node detail and file tree.`
|
|
1456
|
-
}).input(z.object({ nodeId: z.string() })).output(fileTreeNodeSchema),
|
|
1457
|
-
listFiles: oc.route({
|
|
1458
|
-
method: "GET",
|
|
1459
|
-
path: `${basePath}/{nodeId}/files`,
|
|
1460
|
-
tags: [tag],
|
|
1461
|
-
summary: `List ${label} files`,
|
|
1462
|
-
successDescription: `Asset-backed files mounted under the ${label} node.`
|
|
1463
|
-
}).input(z.object({ nodeId: z.string() })).output(z.array(fileTreeFileSchema)),
|
|
1464
|
-
readFile: oc.route({
|
|
1465
|
-
method: "GET",
|
|
1466
|
-
path: `${basePath}/{nodeId}/files/{+filePath}`,
|
|
1467
|
-
tags: [tag],
|
|
1468
|
-
summary: `Read ${label} file`,
|
|
1469
|
-
successDescription: `${label} file content and content hash.`
|
|
1470
|
-
}).input(z.object({ nodeId: z.string(), filePath: z.string() })).output(
|
|
1471
|
-
z.object({
|
|
1472
|
-
nodeId: z.string(),
|
|
1473
|
-
path: z.string(),
|
|
1474
|
-
encoding: z.enum(["utf8", "url"]),
|
|
1475
|
-
content: z.string(),
|
|
1476
|
-
mimeType: z.string(),
|
|
1477
|
-
assetId: z.string(),
|
|
1478
|
-
displayName: z.string().nullable(),
|
|
1479
|
-
assetUrl: z.string().nullable(),
|
|
1480
|
-
contentHash: z.string()
|
|
1481
|
-
})
|
|
1482
|
-
),
|
|
1483
|
-
createChangeRequest: oc.route({
|
|
1484
|
-
method: "POST",
|
|
1485
|
-
path: `${basePath}/{nodeId}/change-requests`,
|
|
1486
|
-
tags: [tag, "Change Requests"],
|
|
1487
|
-
summary: `Create ${label} change request`,
|
|
1488
|
-
successDescription: `Created file-tree change request for a ${label} node.`
|
|
1489
|
-
}).input(createFileTreeChangeRequestInputSchema.extend({ nodeId: z.string() })).output(changeRequestSchema)
|
|
1490
|
-
};
|
|
1491
|
-
};
|
|
1492
1805
|
|
|
1493
|
-
// ../../packages/busabase-contract/src/domains/
|
|
1494
|
-
var
|
|
1806
|
+
// ../../packages/busabase-contract/src/domains/dump/contract.ts
|
|
1807
|
+
var dumpContract = {
|
|
1808
|
+
exportTables: oc.route({
|
|
1809
|
+
method: "POST",
|
|
1810
|
+
path: "/dump/export/tables",
|
|
1811
|
+
tags: ["Dump"],
|
|
1812
|
+
summary: "Export raw rows for one table (cursor-paginated)",
|
|
1813
|
+
successDescription: "A page of raw rows for the requested table (id-ordered), plus an opaque nextCursor (null at the end). Vault items and webhook secrets are never exportable through this endpoint."
|
|
1814
|
+
}).input(ExportTablesInputSchema).output(ExportTablesVOSchema),
|
|
1815
|
+
importBegin: oc.route({
|
|
1816
|
+
method: "POST",
|
|
1817
|
+
path: "/dump/import/begin",
|
|
1818
|
+
tags: ["Dump"],
|
|
1819
|
+
summary: "Begin a full-fidelity import session",
|
|
1820
|
+
successDescription: "Created an import session for the current space. Refused unless the space's node tree is empty (full-fidelity import preserves original ids and cannot merge into existing data)."
|
|
1821
|
+
}).output(ImportBeginVOSchema),
|
|
1822
|
+
importTables: oc.route({
|
|
1823
|
+
method: "POST",
|
|
1824
|
+
path: "/dump/import/tables",
|
|
1825
|
+
tags: ["Dump"],
|
|
1826
|
+
summary: "Import a batch of raw rows into an open session",
|
|
1827
|
+
successDescription: "Inserted rows preserving their original ids. `docBodies` is a pseudo-table ({nodeId, markdown}[]) written directly to object storage (doc bodies are not a DB row)."
|
|
1828
|
+
}).input(ImportTablesInputSchema).output(ImportTablesVOSchema),
|
|
1829
|
+
importCommit: oc.route({
|
|
1830
|
+
method: "POST",
|
|
1831
|
+
path: "/dump/import/commit",
|
|
1832
|
+
tags: ["Dump"],
|
|
1833
|
+
summary: "Finalize an import session",
|
|
1834
|
+
successDescription: "Ran integrity checks (FK orphans, missing blobs) and closed the session. `ok:false` or a non-empty `warnings` array means the import completed but may be incomplete \u2014 inspect before relying on the space."
|
|
1835
|
+
}).input(ImportSessionInputSchema).output(ImportCommitVOSchema),
|
|
1836
|
+
importAbort: oc.route({
|
|
1837
|
+
method: "POST",
|
|
1838
|
+
path: "/dump/import/abort",
|
|
1839
|
+
tags: ["Dump"],
|
|
1840
|
+
summary: "Abort an import session",
|
|
1841
|
+
successDescription: "Best-effort cleanup of everything written so far in this session. Only ever touches the target space validated empty at `importBegin` time."
|
|
1842
|
+
}).input(ImportSessionInputSchema).output(ImportAbortVOSchema)
|
|
1843
|
+
};
|
|
1495
1844
|
z.object({
|
|
1496
1845
|
assetId: z.string()
|
|
1497
1846
|
});
|
|
@@ -1506,7 +1855,12 @@ var createFileNodeInputSchema = z.object({
|
|
|
1506
1855
|
slug: z.string().min(1).regex(/^[a-z0-9-]+$/),
|
|
1507
1856
|
name: z.string().min(1),
|
|
1508
1857
|
description: z.string().optional().default(""),
|
|
1509
|
-
assetId: z.string().min(1)
|
|
1858
|
+
assetId: z.string().min(1),
|
|
1859
|
+
// Review-first by default: without `autoMerge: true`, this proposes the File
|
|
1860
|
+
// node as a pending ChangeRequest (status "in_review") instead of creating it
|
|
1861
|
+
// immediately. Pass `autoMerge: true` only for callers that don't need human
|
|
1862
|
+
// review (seed/migration scripts, an explicit no-review agent task).
|
|
1863
|
+
autoMerge: z.boolean().optional().default(false)
|
|
1510
1864
|
});
|
|
1511
1865
|
var fileContract = {
|
|
1512
1866
|
list: oc.route({
|
|
@@ -1521,8 +1875,8 @@ var fileContract = {
|
|
|
1521
1875
|
path: "/files",
|
|
1522
1876
|
tags: ["Files"],
|
|
1523
1877
|
summary: "Create File node",
|
|
1524
|
-
successDescription: "
|
|
1525
|
-
}).input(createFileNodeInputSchema).output(FileNodeVOSchema),
|
|
1878
|
+
successDescription: "Review-first by default: a pending ChangeRequest proposing the File node. Returns the materialized File node instead when `autoMerge: true` is passed."
|
|
1879
|
+
}).input(createFileNodeInputSchema).output(z.union([FileNodeVOSchema, changeRequestSchema])),
|
|
1526
1880
|
get: oc.route({
|
|
1527
1881
|
method: "GET",
|
|
1528
1882
|
path: "/files/{nodeId}",
|
|
@@ -1630,6 +1984,303 @@ var vaultContract = {
|
|
|
1630
1984
|
successDescription: "Removed local Vault secrets and variables."
|
|
1631
1985
|
}).output(VaultSuccessSchema)
|
|
1632
1986
|
};
|
|
1987
|
+
var WebhookEventTypeSchema = z.enum([
|
|
1988
|
+
"record.created",
|
|
1989
|
+
"ai_mention",
|
|
1990
|
+
"changes_requested",
|
|
1991
|
+
"asset.uploaded"
|
|
1992
|
+
]);
|
|
1993
|
+
z.enum(["webhook", "notify_agent", "run_function"]);
|
|
1994
|
+
var WebhookDeliveryStatusSchema = z.enum(["success", "failed", "skipped"]);
|
|
1995
|
+
var WebhookHttpConfigSchema = z.object({
|
|
1996
|
+
targetUrl: z.string().url(),
|
|
1997
|
+
secret: z.string().min(1).max(256).optional(),
|
|
1998
|
+
headers: z.record(z.string(), z.string()).optional()
|
|
1999
|
+
});
|
|
2000
|
+
var WebhookFunctionConfigSchema = z.object({
|
|
2001
|
+
code: z.string().min(1).max(2e4),
|
|
2002
|
+
timeoutMs: z.number().int().min(100).max(5e3).default(2e3)
|
|
2003
|
+
});
|
|
2004
|
+
var WebhookHttpConfigVOSchema = z.object({
|
|
2005
|
+
targetUrl: z.string().url(),
|
|
2006
|
+
hasSecret: z.boolean(),
|
|
2007
|
+
headers: z.record(z.string(), z.string()).optional()
|
|
2008
|
+
});
|
|
2009
|
+
var WebhookFunctionConfigVOSchema = WebhookFunctionConfigSchema;
|
|
2010
|
+
var webhookRuleCommonInputFields = {
|
|
2011
|
+
name: z.string().min(1).max(200),
|
|
2012
|
+
eventType: WebhookEventTypeSchema,
|
|
2013
|
+
baseId: z.string().nullable().optional(),
|
|
2014
|
+
enabled: z.boolean().default(true)
|
|
2015
|
+
};
|
|
2016
|
+
var WebhookRuleInputSchema = z.discriminatedUnion("actionKind", [
|
|
2017
|
+
z.object({
|
|
2018
|
+
...webhookRuleCommonInputFields,
|
|
2019
|
+
actionKind: z.literal("webhook"),
|
|
2020
|
+
config: WebhookHttpConfigSchema
|
|
2021
|
+
}),
|
|
2022
|
+
z.object({
|
|
2023
|
+
...webhookRuleCommonInputFields,
|
|
2024
|
+
actionKind: z.literal("notify_agent"),
|
|
2025
|
+
config: WebhookHttpConfigSchema
|
|
2026
|
+
}),
|
|
2027
|
+
z.object({
|
|
2028
|
+
...webhookRuleCommonInputFields,
|
|
2029
|
+
actionKind: z.literal("run_function"),
|
|
2030
|
+
config: WebhookFunctionConfigSchema
|
|
2031
|
+
})
|
|
2032
|
+
]);
|
|
2033
|
+
var WebhookRuleUpdateInputSchema = z.discriminatedUnion("actionKind", [
|
|
2034
|
+
z.object({
|
|
2035
|
+
id: z.string(),
|
|
2036
|
+
...webhookRuleCommonInputFields,
|
|
2037
|
+
actionKind: z.literal("webhook"),
|
|
2038
|
+
config: WebhookHttpConfigSchema
|
|
2039
|
+
}),
|
|
2040
|
+
z.object({
|
|
2041
|
+
id: z.string(),
|
|
2042
|
+
...webhookRuleCommonInputFields,
|
|
2043
|
+
actionKind: z.literal("notify_agent"),
|
|
2044
|
+
config: WebhookHttpConfigSchema
|
|
2045
|
+
}),
|
|
2046
|
+
z.object({
|
|
2047
|
+
id: z.string(),
|
|
2048
|
+
...webhookRuleCommonInputFields,
|
|
2049
|
+
actionKind: z.literal("run_function"),
|
|
2050
|
+
config: WebhookFunctionConfigSchema
|
|
2051
|
+
})
|
|
2052
|
+
]);
|
|
2053
|
+
var webhookRuleVOCommonFields = {
|
|
2054
|
+
id: z.string(),
|
|
2055
|
+
spaceId: z.string(),
|
|
2056
|
+
baseId: z.string().nullable(),
|
|
2057
|
+
name: z.string(),
|
|
2058
|
+
eventType: WebhookEventTypeSchema,
|
|
2059
|
+
enabled: z.boolean(),
|
|
2060
|
+
createdBy: z.string(),
|
|
2061
|
+
createdAt: z.string(),
|
|
2062
|
+
updatedAt: z.string(),
|
|
2063
|
+
lastTriggeredAt: z.string().nullable(),
|
|
2064
|
+
lastStatus: WebhookDeliveryStatusSchema.nullable()
|
|
2065
|
+
};
|
|
2066
|
+
var WebhookRuleVOSchema = z.discriminatedUnion("actionKind", [
|
|
2067
|
+
z.object({
|
|
2068
|
+
...webhookRuleVOCommonFields,
|
|
2069
|
+
actionKind: z.literal("webhook"),
|
|
2070
|
+
config: WebhookHttpConfigVOSchema
|
|
2071
|
+
}),
|
|
2072
|
+
z.object({
|
|
2073
|
+
...webhookRuleVOCommonFields,
|
|
2074
|
+
actionKind: z.literal("notify_agent"),
|
|
2075
|
+
config: WebhookHttpConfigVOSchema
|
|
2076
|
+
}),
|
|
2077
|
+
z.object({
|
|
2078
|
+
...webhookRuleVOCommonFields,
|
|
2079
|
+
actionKind: z.literal("run_function"),
|
|
2080
|
+
config: WebhookFunctionConfigVOSchema
|
|
2081
|
+
})
|
|
2082
|
+
]);
|
|
2083
|
+
var WebhookDeliveryVOSchema = z.object({
|
|
2084
|
+
id: z.string(),
|
|
2085
|
+
ruleId: z.string(),
|
|
2086
|
+
eventType: WebhookEventTypeSchema,
|
|
2087
|
+
status: WebhookDeliveryStatusSchema,
|
|
2088
|
+
httpStatus: z.number().nullable(),
|
|
2089
|
+
detail: z.string().nullable(),
|
|
2090
|
+
durationMs: z.number().nullable(),
|
|
2091
|
+
createdAt: z.string()
|
|
2092
|
+
});
|
|
2093
|
+
z.object({}).optional().default({});
|
|
2094
|
+
var ListWebhookDeliveriesInputSchema = z.object({
|
|
2095
|
+
ruleId: z.string(),
|
|
2096
|
+
limit: z.coerce.number().int().min(1).max(100).default(20)
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
// ../../packages/busabase-contract/src/domains/webhook/contract.ts
|
|
2100
|
+
var webhookContract = {
|
|
2101
|
+
list: oc.route({
|
|
2102
|
+
method: "GET",
|
|
2103
|
+
path: "/webhooks",
|
|
2104
|
+
tags: ["Webhooks"],
|
|
2105
|
+
summary: "List webhook automation rules",
|
|
2106
|
+
successDescription: "Configured webhook automation rules for this space."
|
|
2107
|
+
}).output(WebhookRuleVOSchema.array()),
|
|
2108
|
+
get: oc.route({
|
|
2109
|
+
method: "GET",
|
|
2110
|
+
path: "/webhooks/{id}",
|
|
2111
|
+
tags: ["Webhooks"],
|
|
2112
|
+
summary: "Get webhook automation rule",
|
|
2113
|
+
successDescription: "A single webhook automation rule."
|
|
2114
|
+
}).input(z.object({ id: z.string() })).output(WebhookRuleVOSchema),
|
|
2115
|
+
create: oc.route({
|
|
2116
|
+
method: "POST",
|
|
2117
|
+
path: "/webhooks",
|
|
2118
|
+
tags: ["Webhooks"],
|
|
2119
|
+
summary: "Create webhook automation rule",
|
|
2120
|
+
successDescription: "Created webhook automation rule. Dispatches on the configured event via an HTTP webhook, an agent notification, or a sandboxed function."
|
|
2121
|
+
}).input(WebhookRuleInputSchema).output(WebhookRuleVOSchema),
|
|
2122
|
+
update: oc.route({
|
|
2123
|
+
method: "PUT",
|
|
2124
|
+
path: "/webhooks/{id}",
|
|
2125
|
+
tags: ["Webhooks"],
|
|
2126
|
+
summary: "Update webhook automation rule",
|
|
2127
|
+
successDescription: "Updated webhook automation rule."
|
|
2128
|
+
}).input(WebhookRuleUpdateInputSchema).output(WebhookRuleVOSchema),
|
|
2129
|
+
delete: oc.route({
|
|
2130
|
+
method: "DELETE",
|
|
2131
|
+
path: "/webhooks/{id}",
|
|
2132
|
+
tags: ["Webhooks"],
|
|
2133
|
+
summary: "Delete webhook automation rule",
|
|
2134
|
+
successDescription: "Removed the webhook automation rule."
|
|
2135
|
+
}).input(z.object({ id: z.string() })).output(z.object({ success: z.boolean() })),
|
|
2136
|
+
deliveries: oc.route({
|
|
2137
|
+
method: "GET",
|
|
2138
|
+
path: "/webhooks/{ruleId}/deliveries",
|
|
2139
|
+
tags: ["Webhooks"],
|
|
2140
|
+
summary: "List webhook rule delivery attempts",
|
|
2141
|
+
successDescription: "Recent delivery attempts for a webhook rule, newest first."
|
|
2142
|
+
}).input(ListWebhookDeliveriesInputSchema).output(WebhookDeliveryVOSchema.array()),
|
|
2143
|
+
testFire: oc.route({
|
|
2144
|
+
method: "POST",
|
|
2145
|
+
path: "/webhooks/{id}/test-fire",
|
|
2146
|
+
tags: ["Webhooks"],
|
|
2147
|
+
summary: "Test-fire a webhook automation rule",
|
|
2148
|
+
successDescription: "The delivery record produced by firing this rule right now with a synthetic payload \u2014 runs regardless of the rule's enabled state or its real trigger."
|
|
2149
|
+
}).input(z.object({ id: z.string() })).output(WebhookDeliveryVOSchema)
|
|
2150
|
+
};
|
|
2151
|
+
var activityItemSchema = z.discriminatedUnion("kind", [
|
|
2152
|
+
z.object({
|
|
2153
|
+
kind: z.literal("change_request"),
|
|
2154
|
+
timestamp: z.string(),
|
|
2155
|
+
changeRequest: changeRequestSchema
|
|
2156
|
+
}),
|
|
2157
|
+
z.object({
|
|
2158
|
+
kind: z.literal("operation"),
|
|
2159
|
+
timestamp: z.string(),
|
|
2160
|
+
operationId: z.string(),
|
|
2161
|
+
changeRequest: changeRequestSchema
|
|
2162
|
+
}),
|
|
2163
|
+
z.object({
|
|
2164
|
+
kind: z.literal("record"),
|
|
2165
|
+
timestamp: z.string(),
|
|
2166
|
+
record: recordSchema
|
|
2167
|
+
}),
|
|
2168
|
+
z.object({
|
|
2169
|
+
kind: z.literal("audit"),
|
|
2170
|
+
timestamp: z.string(),
|
|
2171
|
+
auditEvent: auditEventSchema,
|
|
2172
|
+
record: recordSchema.nullable()
|
|
2173
|
+
})
|
|
2174
|
+
]);
|
|
2175
|
+
var listActivityPagedInputSchema = z.object({
|
|
2176
|
+
limit: z.coerce.number().int().min(1).max(100).optional().default(50),
|
|
2177
|
+
cursor: z.string().optional()
|
|
2178
|
+
}).optional().default({ limit: 50 });
|
|
2179
|
+
var listActivityResponseSchema = z.object({
|
|
2180
|
+
items: z.array(activityItemSchema),
|
|
2181
|
+
nextCursor: z.string().nullable()
|
|
2182
|
+
});
|
|
2183
|
+
var GrepSourceSchema = z.enum(["files", "docs", "records"]);
|
|
2184
|
+
var UnifiedGrepFilesScopeSchema = z.object({
|
|
2185
|
+
assetIds: z.array(z.string()).optional(),
|
|
2186
|
+
/** Drive/Skill mounted path prefix (matches `busabase_asset_usages.path`). */
|
|
2187
|
+
drivePath: z.string().optional(),
|
|
2188
|
+
mimeTypes: z.array(z.string()).optional()
|
|
2189
|
+
});
|
|
2190
|
+
var UnifiedGrepDocsScopeSchema = z.object({
|
|
2191
|
+
nodeIds: z.array(z.string()).optional()
|
|
2192
|
+
});
|
|
2193
|
+
var UnifiedGrepRecordsScopeSchema = z.object({
|
|
2194
|
+
baseIds: z.array(z.string()).optional(),
|
|
2195
|
+
baseSlugs: z.array(z.string()).optional()
|
|
2196
|
+
});
|
|
2197
|
+
var UnifiedGrepScopeSchema = z.object({
|
|
2198
|
+
files: UnifiedGrepFilesScopeSchema.optional(),
|
|
2199
|
+
docs: UnifiedGrepDocsScopeSchema.optional(),
|
|
2200
|
+
records: UnifiedGrepRecordsScopeSchema.optional()
|
|
2201
|
+
});
|
|
2202
|
+
var UnifiedGrepInputSchema = z.object({
|
|
2203
|
+
pattern: z.string().min(1),
|
|
2204
|
+
/** JS RegExp flags, e.g. `"i"` for case-insensitive — same language as `assets.grep`. */
|
|
2205
|
+
flags: z.string().optional().default(""),
|
|
2206
|
+
/** Which sources to scan. Omitted = all three (`files`, `docs`, `records`). */
|
|
2207
|
+
sources: z.array(GrepSourceSchema).optional(),
|
|
2208
|
+
scope: UnifiedGrepScopeSchema.optional(),
|
|
2209
|
+
/** Shared across every scanned source — files run to completion first, then docs, then whatever remains goes to records. */
|
|
2210
|
+
maxMatches: z.coerce.number().int().min(1).max(GREP_HARD_MAX_MATCHES).optional().default(GREP_DEFAULT_MAX_MATCHES),
|
|
2211
|
+
contextLines: z.coerce.number().int().min(0).max(GREP_MAX_CONTEXT_LINES).optional().default(GREP_DEFAULT_CONTEXT_LINES)
|
|
2212
|
+
});
|
|
2213
|
+
var grepHitFields = {
|
|
2214
|
+
line: z.number().int().positive(),
|
|
2215
|
+
/** 1-based character column (not byte offset) of the match start within the line. */
|
|
2216
|
+
column: z.number().int().positive(),
|
|
2217
|
+
/** The matching line, truncated if it exceeds the long-line guard. */
|
|
2218
|
+
text: z.string(),
|
|
2219
|
+
before: z.array(z.string()),
|
|
2220
|
+
after: z.array(z.string())
|
|
2221
|
+
};
|
|
2222
|
+
var UnifiedGrepFileMatchVOSchema = z.object({
|
|
2223
|
+
source: z.literal("files"),
|
|
2224
|
+
assetId: z.string(),
|
|
2225
|
+
fileName: z.string(),
|
|
2226
|
+
/** Drive/Skill mounted path, or "" when the asset isn't path-mounted (e.g. a File node). */
|
|
2227
|
+
drivePath: z.string(),
|
|
2228
|
+
...grepHitFields
|
|
2229
|
+
});
|
|
2230
|
+
var UnifiedGrepDocMatchVOSchema = z.object({
|
|
2231
|
+
source: z.literal("docs"),
|
|
2232
|
+
nodeId: z.string(),
|
|
2233
|
+
slug: z.string(),
|
|
2234
|
+
name: z.string(),
|
|
2235
|
+
...grepHitFields
|
|
2236
|
+
});
|
|
2237
|
+
var UnifiedGrepRecordMatchVOSchema = z.object({
|
|
2238
|
+
source: z.literal("records"),
|
|
2239
|
+
baseId: z.string(),
|
|
2240
|
+
baseSlug: z.string(),
|
|
2241
|
+
recordId: z.string(),
|
|
2242
|
+
fieldSlug: z.string(),
|
|
2243
|
+
...grepHitFields
|
|
2244
|
+
});
|
|
2245
|
+
var UnifiedGrepMatchVOSchema = z.discriminatedUnion("source", [
|
|
2246
|
+
UnifiedGrepFileMatchVOSchema,
|
|
2247
|
+
UnifiedGrepDocMatchVOSchema,
|
|
2248
|
+
UnifiedGrepRecordMatchVOSchema
|
|
2249
|
+
]);
|
|
2250
|
+
var UnifiedGrepFilesCoverageSchema = z.object({
|
|
2251
|
+
scanned: z.number().int().nonnegative(),
|
|
2252
|
+
missing: z.array(z.string()),
|
|
2253
|
+
stale: z.array(z.string()),
|
|
2254
|
+
unsearchable: z.number().int().nonnegative(),
|
|
2255
|
+
errored: z.array(z.string()),
|
|
2256
|
+
notReached: z.number().int().nonnegative()
|
|
2257
|
+
});
|
|
2258
|
+
var UnifiedGrepDocsCoverageSchema = z.object({
|
|
2259
|
+
scanned: z.number().int().nonnegative(),
|
|
2260
|
+
/** Doc node ids whose body read/scan was attempted but failed — NOT a clean "scanned, no match". */
|
|
2261
|
+
errored: z.array(z.string()),
|
|
2262
|
+
/** Count of in-scope docs the scan never reached because the deadline/maxMatches budget ran out first. */
|
|
2263
|
+
notReached: z.number().int().nonnegative()
|
|
2264
|
+
});
|
|
2265
|
+
var UnifiedGrepRecordsCoverageSchema = z.object({
|
|
2266
|
+
scanned: z.number().int().nonnegative(),
|
|
2267
|
+
/** Record ids whose commit-fields read/flatten/scan was attempted but failed — NOT a clean "scanned, no match". */
|
|
2268
|
+
errored: z.array(z.string()),
|
|
2269
|
+
/** Count of in-scope records the scan never reached because the deadline/maxMatches budget ran out first. */
|
|
2270
|
+
notReached: z.number().int().nonnegative()
|
|
2271
|
+
});
|
|
2272
|
+
var UnifiedGrepCoverageSchema = z.object({
|
|
2273
|
+
files: UnifiedGrepFilesCoverageSchema,
|
|
2274
|
+
docs: UnifiedGrepDocsCoverageSchema,
|
|
2275
|
+
records: UnifiedGrepRecordsCoverageSchema
|
|
2276
|
+
});
|
|
2277
|
+
var UnifiedGrepResultVOSchema = z.object({
|
|
2278
|
+
/** Deterministic order: every `files` match, then every `docs` match, then every `records` match. */
|
|
2279
|
+
matches: z.array(UnifiedGrepMatchVOSchema),
|
|
2280
|
+
coverage: UnifiedGrepCoverageSchema,
|
|
2281
|
+
/** True when any source truncated, or any source has `notReached > 0`. */
|
|
2282
|
+
truncated: z.boolean()
|
|
2283
|
+
});
|
|
1633
2284
|
|
|
1634
2285
|
// ../../packages/busabase-contract/src/contract/busabase.ts
|
|
1635
2286
|
var changeRequestBatchResultSchema = z.object({
|
|
@@ -1659,6 +2310,18 @@ var busabaseContractRoutes = {
|
|
|
1659
2310
|
summary: "Search Busabase",
|
|
1660
2311
|
successDescription: "Paginated search results across records, change requests, Bases, File nodes, and Assets."
|
|
1661
2312
|
}).input(searchInputSchema).output(searchResponseSchema),
|
|
2313
|
+
// Unified Grep (P2a files+docs, P2b records) — top-level, cross-source
|
|
2314
|
+
// superset of `assets.grep`. See apps/busabase/content/spec/unified-grep.md.
|
|
2315
|
+
// Composes `logic/grep.ts`; `assets.grep` (files-only specialist) is
|
|
2316
|
+
// unchanged and stays the dedicated endpoint for its fuller
|
|
2317
|
+
// missing/stale/unsearchable reporting.
|
|
2318
|
+
grep: oc.route({
|
|
2319
|
+
method: "POST",
|
|
2320
|
+
path: "/grep",
|
|
2321
|
+
tags: ["Search"],
|
|
2322
|
+
summary: "Search files, Docs, and Base records with one pattern (unified grep)",
|
|
2323
|
+
successDescription: "Streaming regex/literal matches across every in-scope source \u2014 Drive/Skill files, Doc bodies, and Base records (canonical headCommit.fields, never the truncated search projection) \u2014 with one shared pattern, one shared maxMatches/deadline budget (files scanned first, then docs, then whatever budget remains goes to records), and a per-source honest coverage report (files keeps its existing missing/stale/unsearchable/errored/notReached; docs and records report scanned/errored/notReached). truncated is set when any source truncated or has notReached > 0."
|
|
2324
|
+
}).input(UnifiedGrepInputSchema).output(UnifiedGrepResultVOSchema),
|
|
1662
2325
|
nodes: {
|
|
1663
2326
|
list: oc.route({
|
|
1664
2327
|
method: "GET",
|
|
@@ -1681,6 +2344,13 @@ var busabaseContractRoutes = {
|
|
|
1681
2344
|
summary: "Create Node tree change request",
|
|
1682
2345
|
successDescription: "Created change request for folder or node tree changes."
|
|
1683
2346
|
}).input(createNodeChangeRequestInputSchema).output(changeRequestSchema),
|
|
2347
|
+
move: oc.route({
|
|
2348
|
+
method: "POST",
|
|
2349
|
+
path: "/nodes/{nodeId}/move",
|
|
2350
|
+
tags: ["Nodes"],
|
|
2351
|
+
summary: "Move or reorder a node",
|
|
2352
|
+
successDescription: "Merged change request that repositioned the node under its (optionally new) parent. Applied immediately (auto-merged) since reordering is a low-risk structural tweak, not a review-worthy content change."
|
|
2353
|
+
}).input(moveNodeInputSchema).output(changeRequestSchema),
|
|
1684
2354
|
purge: oc.route({
|
|
1685
2355
|
method: "DELETE",
|
|
1686
2356
|
path: "/nodes/{nodeId}",
|
|
@@ -1705,6 +2375,15 @@ var busabaseContractRoutes = {
|
|
|
1705
2375
|
successDescription: "Recorded audit event."
|
|
1706
2376
|
}).input(createAuditEventInputSchema).output(auditEventSchema)
|
|
1707
2377
|
},
|
|
2378
|
+
activity: {
|
|
2379
|
+
listPaged: oc.route({
|
|
2380
|
+
method: "GET",
|
|
2381
|
+
path: "/activity/paged",
|
|
2382
|
+
tags: ["Activity"],
|
|
2383
|
+
summary: "List the activity feed with keyset pagination",
|
|
2384
|
+
successDescription: "A page of activity items (change requests, operations, records and audit events merged, newest first) plus an opaque nextCursor (null at the end)."
|
|
2385
|
+
}).input(listActivityPagedInputSchema).output(listActivityResponseSchema)
|
|
2386
|
+
},
|
|
1708
2387
|
comments: {
|
|
1709
2388
|
list: oc.route({
|
|
1710
2389
|
method: "GET",
|
|
@@ -1738,11 +2417,14 @@ var busabaseContractRoutes = {
|
|
|
1738
2417
|
bases: baseContract,
|
|
1739
2418
|
skills: skillContract,
|
|
1740
2419
|
drives: driveContract,
|
|
2420
|
+
airapps: airappContract,
|
|
1741
2421
|
files: fileContract,
|
|
1742
2422
|
docs: docContract,
|
|
1743
2423
|
folders: folderContract,
|
|
1744
2424
|
assets: assetsContract,
|
|
1745
2425
|
vault: vaultContract,
|
|
2426
|
+
webhooks: webhookContract,
|
|
2427
|
+
dump: dumpContract,
|
|
1746
2428
|
changeRequests: {
|
|
1747
2429
|
list: oc.route({
|
|
1748
2430
|
method: "GET",
|
|
@@ -2069,10 +2751,61 @@ var Busabase = class {
|
|
|
2069
2751
|
get agentTasks() {
|
|
2070
2752
|
return this.client.agentTasks;
|
|
2071
2753
|
}
|
|
2754
|
+
get webhooks() {
|
|
2755
|
+
return this.client.webhooks;
|
|
2756
|
+
}
|
|
2072
2757
|
/** Full-text search across records, change requests, and Bases. */
|
|
2073
2758
|
search(input) {
|
|
2074
2759
|
return this.client.search(input);
|
|
2075
2760
|
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Unified grep — one regex/literal pattern scanned across every in-scope
|
|
2763
|
+
* source (Drive/Skill files, Doc bodies, and Base records — records read
|
|
2764
|
+
* the canonical `headCommit.fields`, never the truncated search
|
|
2765
|
+
* projection), with a shared `maxMatches`/deadline budget and per-source
|
|
2766
|
+
* honest coverage. Use this when the answer could live anywhere; use
|
|
2767
|
+
* `client.assets.grep` directly instead when you specifically only care
|
|
2768
|
+
* about files and want its fuller `missing`/`stale`/`unsearchable`
|
|
2769
|
+
* file-only reporting.
|
|
2770
|
+
*/
|
|
2771
|
+
grep(input) {
|
|
2772
|
+
return this.client.grep(input);
|
|
2773
|
+
}
|
|
2774
|
+
/**
|
|
2775
|
+
* Supply text for an Asset's Drive Grep Retrieval text slot in one call —
|
|
2776
|
+
* inline for small text, a presigned upload for large text — so callers
|
|
2777
|
+
* never see the underlying three-step flow
|
|
2778
|
+
* (`createTextUploadUrl` → PUT bytes → `putText({ storageKey })`).
|
|
2779
|
+
*
|
|
2780
|
+
* @example
|
|
2781
|
+
* ```ts
|
|
2782
|
+
* await bb.putText(assetId, extractedText); // picks inline vs presigned by size
|
|
2783
|
+
* ```
|
|
2784
|
+
*/
|
|
2785
|
+
async putText(assetId, text) {
|
|
2786
|
+
const INLINE_TEXT_MAX_BYTES = 1024 * 1024;
|
|
2787
|
+
const byteLength = typeof Buffer !== "undefined" ? Buffer.byteLength(text, "utf8") : new Blob([text]).size;
|
|
2788
|
+
if (byteLength <= INLINE_TEXT_MAX_BYTES) {
|
|
2789
|
+
return this.client.assets.putText({ assetId, text });
|
|
2790
|
+
}
|
|
2791
|
+
const upload = await this.client.assets.createTextUploadUrl({
|
|
2792
|
+
assetId,
|
|
2793
|
+
sizeBytes: byteLength
|
|
2794
|
+
});
|
|
2795
|
+
const doFetch = this.config.fetch ?? fetch;
|
|
2796
|
+
const response = await doFetch(upload.uploadUrl, {
|
|
2797
|
+
method: "PUT",
|
|
2798
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
2799
|
+
body: text
|
|
2800
|
+
});
|
|
2801
|
+
if (!response.ok) {
|
|
2802
|
+
const detail = await response.text().catch(() => "");
|
|
2803
|
+
throw new Error(
|
|
2804
|
+
`putText: presigned upload failed (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
return this.client.assets.putText({ assetId, storageKey: upload.storageKey });
|
|
2808
|
+
}
|
|
2076
2809
|
/** Service health — reaches the server without requiring auth. */
|
|
2077
2810
|
health() {
|
|
2078
2811
|
return this.client.system.health();
|