@agentshouse/kit 0.1.0-alpha.26 → 0.1.0-alpha.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -136
- package/bin/kit.js +48 -46
- package/bootstrap.json +3 -3
- package/package.json +1 -1
- package/src/configuration.js +9 -209
- package/src/connector-login.js +3 -84
- package/src/contract.js +4 -0
- package/src/delivery.js +4 -7
- package/src/google/calendar.js +39 -45
- package/src/google/drive.js +61 -186
- package/src/google/entries.js +346 -0
- package/src/google/gateway.js +3 -6
- package/src/google/gmail.js +33 -23
- package/src/google/oauth.js +106 -66
- package/src/google/source.js +21 -23
- package/src/house.js +43 -5
- package/src/kit-work-stream.js +1 -1
- package/src/setup.js +8 -14
- package/src/source.js +51 -26
- package/src/state.js +19 -1
- package/src/tasks.js +72 -0
- package/src/worker.js +26 -60
- package/src/working-copy.js +1 -15
- package/src/google/declared.js +0 -146
- package/src/google/id.js +0 -1
- package/src/instance/google.js +0 -1
package/src/source.js
CHANGED
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
stallOf,
|
|
14
14
|
} from "./delivery.js";
|
|
15
15
|
import { captureBatch, maintainControl, openSession } from "./house.js";
|
|
16
|
-
import {
|
|
16
|
+
import { GOOGLE_SOURCES } from "./google/oauth.js";
|
|
17
|
+
import { googlePage, recordAccountEmail } from "./google/source.js";
|
|
17
18
|
import { houseAnswered, houseAnsweredFailure, houseFailed, KitRefusal } from "./refusal.js";
|
|
18
19
|
import { recordAcceptedOriginals, sameReference, saveSourceOriginals } from "./source-originals.js";
|
|
19
20
|
import {
|
|
@@ -29,14 +30,15 @@ import {
|
|
|
29
30
|
writeCheckpoint,
|
|
30
31
|
} from "./state.js";
|
|
31
32
|
|
|
33
|
+
const GOOGLE = Object.freeze({ id: GOOGLE_SOURCES, preset: "google" });
|
|
34
|
+
|
|
32
35
|
async function acquire(context, service, checkpoint, limit) {
|
|
33
|
-
if (
|
|
36
|
+
if (service.google !== undefined) {
|
|
34
37
|
return googlePage(
|
|
35
38
|
{
|
|
36
39
|
directory: context.directory,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
service,
|
|
40
|
+
account: service.google.account,
|
|
41
|
+
service: service.google.service,
|
|
40
42
|
cursor: checkpoint.cursor,
|
|
41
43
|
limit,
|
|
42
44
|
backfillFloor:
|
|
@@ -169,6 +171,7 @@ async function deliverService(context, service) {
|
|
|
169
171
|
context.instance.preset === "custom"
|
|
170
172
|
? { ...context, environment: await resolveEnvironment(service.connector) }
|
|
171
173
|
: context;
|
|
174
|
+
if (service.google !== undefined) await recordAccountEmail(context, service.google);
|
|
172
175
|
await runDeliveryLoop(resolved, service, progress);
|
|
173
176
|
} catch (refusal) {
|
|
174
177
|
if (!(refusal instanceof KitRefusal) || refusal.code === "kit_command_cancelled") throw refusal;
|
|
@@ -197,12 +200,31 @@ async function deliverService(context, service) {
|
|
|
197
200
|
};
|
|
198
201
|
}
|
|
199
202
|
|
|
203
|
+
function initialBackfill(initial) {
|
|
204
|
+
if (initial.new === true) return { kind: "days", bound: 0 };
|
|
205
|
+
if (initial.days !== undefined) return { kind: "days", bound: initial.days };
|
|
206
|
+
if (initial.items !== undefined) return { kind: "count", bound: initial.items };
|
|
207
|
+
return { kind: "all" };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function captureEntry(context, entry) {
|
|
211
|
+
const delivered = await deliverService(
|
|
212
|
+
{ ...context, instance: GOOGLE },
|
|
213
|
+
{
|
|
214
|
+
service: entry.source,
|
|
215
|
+
sourceRef: entry.source,
|
|
216
|
+
rooms: entry.rooms,
|
|
217
|
+
backfill: initialBackfill(entry.initial),
|
|
218
|
+
google: { account: entry.account, service: entry.service },
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
return { ...delivered, service: entry.service };
|
|
222
|
+
}
|
|
223
|
+
|
|
200
224
|
export async function captureSource(options, dependencies = {}) {
|
|
201
225
|
const configuration = await readConfiguration(options.configuration);
|
|
202
226
|
const instance = sourceInstance(configuration, options.instance);
|
|
203
227
|
const services = declaredServices(instance, options.service ?? null);
|
|
204
|
-
const googleOAuthClient =
|
|
205
|
-
instance.preset === "google" ? dependencies.googleOAuthClient() : null;
|
|
206
228
|
const session = await openSession(options, dependencies);
|
|
207
229
|
return maintainControl(
|
|
208
230
|
session,
|
|
@@ -214,7 +236,6 @@ export async function captureSource(options, dependencies = {}) {
|
|
|
214
236
|
signal,
|
|
215
237
|
now: session.now,
|
|
216
238
|
dependencies,
|
|
217
|
-
googleOAuthClient,
|
|
218
239
|
};
|
|
219
240
|
const delivered = [];
|
|
220
241
|
for (const service of services) delivered.push(await deliverService(context, service));
|
|
@@ -224,6 +245,27 @@ export async function captureSource(options, dependencies = {}) {
|
|
|
224
245
|
);
|
|
225
246
|
}
|
|
226
247
|
|
|
248
|
+
export async function localServiceState(directory, instance, service) {
|
|
249
|
+
return {
|
|
250
|
+
health: (await readHealth(directory, instance, service)) ?? {
|
|
251
|
+
state: "pending",
|
|
252
|
+
category: null,
|
|
253
|
+
observed_at: null,
|
|
254
|
+
},
|
|
255
|
+
checkpoint: await readCheckpoint(directory, instance, service),
|
|
256
|
+
gaps: await readCoverageGaps(directory, instance, service),
|
|
257
|
+
dropped: await readDroppedItems(directory, instance, service),
|
|
258
|
+
originals: (await readSourceOriginals(directory, instance, service)).map((reference) => ({
|
|
259
|
+
room: reference.room,
|
|
260
|
+
record: reference.record,
|
|
261
|
+
position: reference.position,
|
|
262
|
+
native_object_id: reference.native_object_id,
|
|
263
|
+
save: reference.save,
|
|
264
|
+
error: reference.error,
|
|
265
|
+
})),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
227
269
|
export async function sourceStatus(options) {
|
|
228
270
|
const configuration = await readConfiguration(options.configuration);
|
|
229
271
|
const instance = sourceInstance(configuration, options.instance);
|
|
@@ -234,24 +276,7 @@ export async function sourceStatus(options) {
|
|
|
234
276
|
service: service.service,
|
|
235
277
|
source_ref: service.sourceRef,
|
|
236
278
|
rooms: service.rooms,
|
|
237
|
-
|
|
238
|
-
state: "pending",
|
|
239
|
-
category: null,
|
|
240
|
-
observed_at: null,
|
|
241
|
-
},
|
|
242
|
-
checkpoint: await readCheckpoint(options.directory, instance.id, service.service),
|
|
243
|
-
gaps: await readCoverageGaps(options.directory, instance.id, service.service),
|
|
244
|
-
dropped: await readDroppedItems(options.directory, instance.id, service.service),
|
|
245
|
-
originals: (await readSourceOriginals(options.directory, instance.id, service.service)).map(
|
|
246
|
-
(reference) => ({
|
|
247
|
-
room: reference.room,
|
|
248
|
-
record: reference.record,
|
|
249
|
-
position: reference.position,
|
|
250
|
-
native_object_id: reference.native_object_id,
|
|
251
|
-
save: reference.save,
|
|
252
|
-
error: reference.error,
|
|
253
|
-
}),
|
|
254
|
-
),
|
|
279
|
+
...(await localServiceState(options.directory, instance.id, service.service)),
|
|
255
280
|
});
|
|
256
281
|
}
|
|
257
282
|
return { instance: instance.id, preset: instance.preset, services: reported };
|
package/src/state.js
CHANGED
|
@@ -9,10 +9,28 @@ function segment(name) {
|
|
|
9
9
|
return name;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
function notePath(directory, instance, service, kind) {
|
|
12
|
+
export function notePath(directory, instance, service, kind) {
|
|
13
13
|
return join(directory, "state", segment(instance), kind, `${segment(service)}.json`);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export async function noteNames(directory, instance, kind) {
|
|
17
|
+
let present;
|
|
18
|
+
try {
|
|
19
|
+
present = await readdir(join(directory, "state", segment(instance), kind));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error.code === "ENOENT") return [];
|
|
22
|
+
throw new KitRefusal("kit_state_unreadable", kind);
|
|
23
|
+
}
|
|
24
|
+
return present
|
|
25
|
+
.filter((name) => name.endsWith(".json"))
|
|
26
|
+
.map((name) => name.slice(0, -".json".length))
|
|
27
|
+
.sort();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function removeNotes(directory, instance, service, kinds) {
|
|
31
|
+
for (const kind of kinds) await rm(notePath(directory, instance, service, kind), { force: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
function repositoryPath(directory, key) {
|
|
17
35
|
if (!/^[a-f0-9]{64}$/.test(key)) throw new KitRefusal("kit_state_name_invalid", key);
|
|
18
36
|
return join(directory, "state", "repositories", `${key}.json`);
|
package/src/tasks.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
|
|
3
|
+
export function coalescedTask(signal, fail, work) {
|
|
4
|
+
let pending = false;
|
|
5
|
+
let task = null;
|
|
6
|
+
const trigger = () => {
|
|
7
|
+
pending = true;
|
|
8
|
+
if (task !== null) return;
|
|
9
|
+
task = (async () => {
|
|
10
|
+
while (pending && !signal.aborted) {
|
|
11
|
+
pending = false;
|
|
12
|
+
await work();
|
|
13
|
+
}
|
|
14
|
+
})()
|
|
15
|
+
.catch((error) => {
|
|
16
|
+
if (signal.aborted) return;
|
|
17
|
+
fail(error);
|
|
18
|
+
})
|
|
19
|
+
.finally(() => {
|
|
20
|
+
task = null;
|
|
21
|
+
if (pending && !signal.aborted) trigger();
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
return { trigger, idle: () => task === null, settled: () => task };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function residentWake() {
|
|
28
|
+
let pending = false;
|
|
29
|
+
let release = null;
|
|
30
|
+
return {
|
|
31
|
+
notify() {
|
|
32
|
+
pending = true;
|
|
33
|
+
release?.();
|
|
34
|
+
},
|
|
35
|
+
async wait(milliseconds, signal) {
|
|
36
|
+
if (pending) {
|
|
37
|
+
pending = false;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const pause = new AbortController();
|
|
41
|
+
try {
|
|
42
|
+
await Promise.race([
|
|
43
|
+
new Promise((resolve) => {
|
|
44
|
+
release = resolve;
|
|
45
|
+
}),
|
|
46
|
+
delay(milliseconds, undefined, { signal: AbortSignal.any([signal, pause.signal]) }),
|
|
47
|
+
]);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (!signal.aborted) throw error;
|
|
50
|
+
} finally {
|
|
51
|
+
release = null;
|
|
52
|
+
pending = false;
|
|
53
|
+
pause.abort();
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const turns = new Map();
|
|
60
|
+
|
|
61
|
+
export function exclusively(path, work) {
|
|
62
|
+
const turn = (turns.get(path) ?? Promise.resolve()).then(work);
|
|
63
|
+
const settled = turn.then(
|
|
64
|
+
() => undefined,
|
|
65
|
+
() => undefined,
|
|
66
|
+
);
|
|
67
|
+
turns.set(path, settled);
|
|
68
|
+
void settled.then(() => {
|
|
69
|
+
if (turns.get(path) === settled) turns.delete(path);
|
|
70
|
+
});
|
|
71
|
+
return turn;
|
|
72
|
+
}
|
package/src/worker.js
CHANGED
|
@@ -20,7 +20,9 @@ import { localRequest, reconcileInbound } from "./inbound.js";
|
|
|
20
20
|
import { localFiles } from "./local-files.js";
|
|
21
21
|
import { answerText, answerYaml, editRefusal } from "./mcp.js";
|
|
22
22
|
import { inheritedEnvironment } from "./command.js";
|
|
23
|
+
import { sourceEntries } from "./google/entries.js";
|
|
23
24
|
import { watchKitWork } from "./kit-work-stream.js";
|
|
25
|
+
import { coalescedTask, residentWake } from "./tasks.js";
|
|
24
26
|
import {
|
|
25
27
|
applyWorkingCopies,
|
|
26
28
|
deliverWorkingCopies,
|
|
@@ -955,62 +957,6 @@ async function reportedWorkingCopies(act, context, signal) {
|
|
|
955
957
|
}
|
|
956
958
|
}
|
|
957
959
|
|
|
958
|
-
function coalescedTask(signal, fail, work) {
|
|
959
|
-
let pending = false;
|
|
960
|
-
let task = null;
|
|
961
|
-
const trigger = () => {
|
|
962
|
-
pending = true;
|
|
963
|
-
if (task !== null) return;
|
|
964
|
-
task = (async () => {
|
|
965
|
-
while (pending && !signal.aborted) {
|
|
966
|
-
pending = false;
|
|
967
|
-
await work();
|
|
968
|
-
}
|
|
969
|
-
})()
|
|
970
|
-
.catch((error) => {
|
|
971
|
-
if (signal.aborted) return;
|
|
972
|
-
fail(error);
|
|
973
|
-
})
|
|
974
|
-
.finally(() => {
|
|
975
|
-
task = null;
|
|
976
|
-
if (pending && !signal.aborted) trigger();
|
|
977
|
-
});
|
|
978
|
-
};
|
|
979
|
-
return { trigger, idle: () => task === null, settled: () => task };
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
function residentWake() {
|
|
983
|
-
let pending = false;
|
|
984
|
-
let release = null;
|
|
985
|
-
return {
|
|
986
|
-
notify() {
|
|
987
|
-
pending = true;
|
|
988
|
-
release?.();
|
|
989
|
-
},
|
|
990
|
-
async wait(milliseconds, signal) {
|
|
991
|
-
if (pending) {
|
|
992
|
-
pending = false;
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
const pause = new AbortController();
|
|
996
|
-
try {
|
|
997
|
-
await Promise.race([
|
|
998
|
-
new Promise((resolve) => {
|
|
999
|
-
release = resolve;
|
|
1000
|
-
}),
|
|
1001
|
-
delay(milliseconds, undefined, { signal: AbortSignal.any([signal, pause.signal]) }),
|
|
1002
|
-
]);
|
|
1003
|
-
} catch (error) {
|
|
1004
|
-
if (!signal.aborted) throw error;
|
|
1005
|
-
} finally {
|
|
1006
|
-
release = null;
|
|
1007
|
-
pending = false;
|
|
1008
|
-
pause.abort();
|
|
1009
|
-
}
|
|
1010
|
-
},
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
960
|
function residentUpdate(context, wake) {
|
|
1015
961
|
let requested = existsSync(updateDrain(context.directory));
|
|
1016
962
|
let ready = null;
|
|
@@ -1078,6 +1024,7 @@ export async function runResident(options, dependencies = {}) {
|
|
|
1078
1024
|
const wake = residentWake();
|
|
1079
1025
|
const update = residentUpdate(context, wake);
|
|
1080
1026
|
context.update = update;
|
|
1027
|
+
let sources = null;
|
|
1081
1028
|
const served = await serveResident(
|
|
1082
1029
|
session,
|
|
1083
1030
|
context.directory,
|
|
@@ -1087,6 +1034,8 @@ export async function runResident(options, dependencies = {}) {
|
|
|
1087
1034
|
pushWorkingCopy({ directory: context.directory, session }, request, ownerCaller(session), signal).catch(
|
|
1088
1035
|
(error) => ({ refused: true, text: formatCommandError(error) }),
|
|
1089
1036
|
),
|
|
1037
|
+
(source) =>
|
|
1038
|
+
sources === null ? Promise.reject(new KitRefusal("kit_resident_unavailable")) : sources.capture(source),
|
|
1090
1039
|
);
|
|
1091
1040
|
const serve = async (signal) => {
|
|
1092
1041
|
context.busy = true;
|
|
@@ -1149,13 +1098,24 @@ export async function runResident(options, dependencies = {}) {
|
|
|
1149
1098
|
const deliveries = coalescedTask(signal, unavailable, () =>
|
|
1150
1099
|
reportedWorkingCopies(deliverWorkingCopies, context, signal),
|
|
1151
1100
|
);
|
|
1101
|
+
sources = sourceEntries({
|
|
1102
|
+
session: context.session,
|
|
1103
|
+
directory: context.directory,
|
|
1104
|
+
dependencies,
|
|
1105
|
+
signal: AbortSignal.any([signal, watcher.signal]),
|
|
1106
|
+
now: session.now,
|
|
1107
|
+
draining: () => update.requested,
|
|
1108
|
+
unavailable: (error) => reported(`google sources unavailable: ${error?.message ?? error}`),
|
|
1109
|
+
});
|
|
1110
|
+
await sources.start();
|
|
1152
1111
|
const pointer = () => {
|
|
1153
1112
|
if (!update.requested) agentRefresh.trigger();
|
|
1154
1113
|
};
|
|
1155
|
-
const available = (subject) => {
|
|
1114
|
+
const available = (subject = null) => {
|
|
1156
1115
|
if (update.requested) return;
|
|
1157
|
-
if (subject
|
|
1158
|
-
if (subject
|
|
1116
|
+
if (subject === null || subject === "agents") agentRefresh.trigger();
|
|
1117
|
+
if (subject === null || subject === "working_copy") copies.trigger();
|
|
1118
|
+
if (subject === null || subject === "sources") sources.reconcile();
|
|
1159
1119
|
};
|
|
1160
1120
|
context.onIdle = pointer;
|
|
1161
1121
|
context.workingCopiesApplied = copies.settled;
|
|
@@ -1180,7 +1140,12 @@ export async function runResident(options, dependencies = {}) {
|
|
|
1180
1140
|
if (sessions.length > 0) update.answer({ ready: false, sessions });
|
|
1181
1141
|
else if (
|
|
1182
1142
|
session.refused ||
|
|
1183
|
-
(!served.serving() &&
|
|
1143
|
+
(!served.serving() &&
|
|
1144
|
+
logins.size === 0 &&
|
|
1145
|
+
agentRefresh.idle() &&
|
|
1146
|
+
copies.idle() &&
|
|
1147
|
+
deliveries.idle() &&
|
|
1148
|
+
sources.idle())
|
|
1184
1149
|
) {
|
|
1185
1150
|
return;
|
|
1186
1151
|
}
|
|
@@ -1194,6 +1159,7 @@ export async function runResident(options, dependencies = {}) {
|
|
|
1194
1159
|
await agentRefresh.settled();
|
|
1195
1160
|
await copies.settled();
|
|
1196
1161
|
await deliveries.settled();
|
|
1162
|
+
await sources.settled();
|
|
1197
1163
|
for (const running of logins.values()) running.controller.abort();
|
|
1198
1164
|
await Promise.all([...logins.values()].map((running) => running.task));
|
|
1199
1165
|
await context.agentRefresh;
|
package/src/working-copy.js
CHANGED
|
@@ -23,6 +23,7 @@ import { readNote, writeNote, writeText } from "./state.js";
|
|
|
23
23
|
import { bootstrapBundle, discoveryOf, door, withSession } from "./sync.js";
|
|
24
24
|
import { isGeneratedProvenance, sidecarPath, structuredOrReplace } from "./sync-library.js";
|
|
25
25
|
import { locate, removeReplicaFile, writeReplicaFile } from "./sync-state.js";
|
|
26
|
+
import { exclusively } from "./tasks.js";
|
|
26
27
|
|
|
27
28
|
const COMMITTER_NAME = "House Kit";
|
|
28
29
|
const COMMITTER_EMAIL = "house-kit@localhost";
|
|
@@ -366,21 +367,6 @@ async function heldCopies(directory) {
|
|
|
366
367
|
return held.filter((copy) => copy !== null);
|
|
367
368
|
}
|
|
368
369
|
|
|
369
|
-
const turns = new Map();
|
|
370
|
-
|
|
371
|
-
function exclusively(path, work) {
|
|
372
|
-
const turn = (turns.get(path) ?? Promise.resolve()).then(work);
|
|
373
|
-
const settled = turn.then(
|
|
374
|
-
() => undefined,
|
|
375
|
-
() => undefined,
|
|
376
|
-
);
|
|
377
|
-
turns.set(path, settled);
|
|
378
|
-
void settled.then(() => {
|
|
379
|
-
if (turns.get(path) === settled) turns.delete(path);
|
|
380
|
-
});
|
|
381
|
-
return turn;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
370
|
function received(scope, read, parents) {
|
|
385
371
|
const files = read.files.filter((file) => file.writable);
|
|
386
372
|
return { message: `House ${housePath(scope)} at position ${read.position}`, parents, files, whole: true };
|
package/src/google/declared.js
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { ITEM_BODY_MAX_BYTES } from "../contract.js";
|
|
2
|
-
import { KitRefusal } from "../refusal.js";
|
|
3
|
-
import { googleJson } from "./gateway.js";
|
|
4
|
-
|
|
5
|
-
function valueAt(value, path) {
|
|
6
|
-
let current = value;
|
|
7
|
-
for (const segment of path.split(".")) {
|
|
8
|
-
if (current === null || current === undefined || typeof current !== "object") return undefined;
|
|
9
|
-
current = current[segment];
|
|
10
|
-
}
|
|
11
|
-
return current;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function requiredText(value, declaration) {
|
|
15
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
16
|
-
throw new KitRefusal("google_provider_response_invalid", declaration);
|
|
17
|
-
}
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function optionalText(value, declaration) {
|
|
22
|
-
if (value === undefined || value === null) return null;
|
|
23
|
-
return requiredText(value, declaration);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function cursorOf(value) {
|
|
27
|
-
if (value === null) return { pageToken: null, checkpoint: null };
|
|
28
|
-
let cursor;
|
|
29
|
-
try {
|
|
30
|
-
cursor = JSON.parse(value);
|
|
31
|
-
} catch {
|
|
32
|
-
throw new KitRefusal("google_checkpoint_invalid", "declared service");
|
|
33
|
-
}
|
|
34
|
-
if (
|
|
35
|
-
typeof cursor !== "object" ||
|
|
36
|
-
cursor === null ||
|
|
37
|
-
(cursor.pageToken !== null && typeof cursor.pageToken !== "string") ||
|
|
38
|
-
(cursor.checkpoint !== null && typeof cursor.checkpoint !== "string")
|
|
39
|
-
) {
|
|
40
|
-
throw new KitRefusal("google_checkpoint_invalid", "declared service");
|
|
41
|
-
}
|
|
42
|
-
return cursor;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function composedBody(item, paths) {
|
|
46
|
-
const parts = paths.map((path) => valueAt(item, path));
|
|
47
|
-
if (parts.some((part) => !["string", "number", "boolean"].includes(typeof part))) {
|
|
48
|
-
throw new KitRefusal("google_provider_response_invalid", "declared body");
|
|
49
|
-
}
|
|
50
|
-
const body = parts.map((part) => `${part}`).join("\n\n");
|
|
51
|
-
if (Buffer.byteLength(body, "utf8") > ITEM_BODY_MAX_BYTES) {
|
|
52
|
-
throw new KitRefusal("google_provider_response_invalid", "declared body");
|
|
53
|
-
}
|
|
54
|
-
return body;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function normalizedItem(service, item) {
|
|
58
|
-
const fields = service.declaration.fields;
|
|
59
|
-
const observedAt = requiredText(valueAt(item, fields.observedAt), "declared observed time");
|
|
60
|
-
if (Number.isNaN(Date.parse(observedAt))) {
|
|
61
|
-
throw new KitRefusal("google_provider_response_invalid", "declared observed time");
|
|
62
|
-
}
|
|
63
|
-
const container = requiredText(valueAt(item, fields.container), "declared container");
|
|
64
|
-
const nativeVersion =
|
|
65
|
-
fields.nativeVersion === null
|
|
66
|
-
? null
|
|
67
|
-
: optionalText(valueAt(item, fields.nativeVersion), "declared native version");
|
|
68
|
-
const providerModifiedAt =
|
|
69
|
-
fields.providerModifiedAt === null
|
|
70
|
-
? null
|
|
71
|
-
: optionalText(
|
|
72
|
-
valueAt(item, fields.providerModifiedAt),
|
|
73
|
-
"declared provider modification time",
|
|
74
|
-
);
|
|
75
|
-
if (providerModifiedAt !== null && Number.isNaN(Date.parse(providerModifiedAt))) {
|
|
76
|
-
throw new KitRefusal("google_provider_response_invalid", "declared provider modification time");
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
namespace: `google/${service.service}`,
|
|
80
|
-
nativeObjectId: requiredText(valueAt(item, fields.nativeId), "declared native id"),
|
|
81
|
-
nativeVersion,
|
|
82
|
-
providerModifiedAt:
|
|
83
|
-
providerModifiedAt === null ? null : new Date(providerModifiedAt).toISOString(),
|
|
84
|
-
observedAt: new Date(observedAt).toISOString(),
|
|
85
|
-
body: composedBody(item, fields.body),
|
|
86
|
-
author:
|
|
87
|
-
fields.author === null
|
|
88
|
-
? null
|
|
89
|
-
: optionalText(valueAt(item, fields.author), "declared author"),
|
|
90
|
-
url:
|
|
91
|
-
fields.url === null ? null : optionalText(valueAt(item, fields.url), "declared url"),
|
|
92
|
-
attachments: [],
|
|
93
|
-
container: `google/${service.service}/${container}`,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export async function declaredPage(options, dependencies = {}) {
|
|
98
|
-
const declared = options.service.declaration;
|
|
99
|
-
const cursor = cursorOf(options.cursor);
|
|
100
|
-
const url = new URL(declared.path, `https://${declared.host}`);
|
|
101
|
-
for (const [name, value] of Object.entries(declared.query)) url.searchParams.set(name, value);
|
|
102
|
-
url.searchParams.set(declared.pageSize, `${options.limit}`);
|
|
103
|
-
if (cursor.pageToken !== null) {
|
|
104
|
-
url.searchParams.set(declared.pageToken.request, cursor.pageToken);
|
|
105
|
-
}
|
|
106
|
-
if (cursor.checkpoint !== null) {
|
|
107
|
-
url.searchParams.set(declared.pageToken.checkpointRequest, cursor.checkpoint);
|
|
108
|
-
}
|
|
109
|
-
const answer = await googleJson(
|
|
110
|
-
{
|
|
111
|
-
directory: options.directory,
|
|
112
|
-
instance: options.instance,
|
|
113
|
-
client: options.client,
|
|
114
|
-
requiredScopes: declared.scopes,
|
|
115
|
-
url: url.toString(),
|
|
116
|
-
signal: options.signal,
|
|
117
|
-
},
|
|
118
|
-
dependencies,
|
|
119
|
-
);
|
|
120
|
-
const entries = valueAt(answer, declared.items);
|
|
121
|
-
if (!Array.isArray(entries) || entries.length > options.limit) {
|
|
122
|
-
throw new KitRefusal("google_provider_response_invalid", "declared items");
|
|
123
|
-
}
|
|
124
|
-
const pageToken = valueAt(answer, declared.pageToken.response);
|
|
125
|
-
let next;
|
|
126
|
-
if (pageToken !== undefined && pageToken !== null) {
|
|
127
|
-
next = {
|
|
128
|
-
pageToken: requiredText(pageToken, "declared page token"),
|
|
129
|
-
checkpoint: cursor.checkpoint,
|
|
130
|
-
};
|
|
131
|
-
} else {
|
|
132
|
-
next = {
|
|
133
|
-
pageToken: null,
|
|
134
|
-
checkpoint: requiredText(
|
|
135
|
-
valueAt(answer, declared.pageToken.checkpointResponse),
|
|
136
|
-
"declared checkpoint",
|
|
137
|
-
),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
return {
|
|
141
|
-
items: entries.map((entry) => normalizedItem(options.service, entry)),
|
|
142
|
-
dropped: [],
|
|
143
|
-
cursor: JSON.stringify(next),
|
|
144
|
-
more: pageToken !== undefined && pageToken !== null,
|
|
145
|
-
};
|
|
146
|
-
}
|
package/src/google/id.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const GOOGLE_DRIVE_ID = /^[A-Za-z0-9_-]{1,512}$/;
|
package/src/instance/google.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const GOOGLE_OAUTH_CLIENT = Object.freeze({"id":"911965338518-867gdvuddb7kdkjo3qvpv6gds369alb7.apps.googleusercontent.com","secret":"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"});
|