agenthooksprotocol 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/src/client/composition.d.ts +2 -0
- package/dist/src/client/composition.d.ts.map +1 -1
- package/dist/src/client/composition.js +14 -6
- package/dist/src/client/composition.js.map +1 -1
- package/dist/src/client/content.d.ts +2 -0
- package/dist/src/client/content.d.ts.map +1 -1
- package/dist/src/client/content.js +18 -7
- package/dist/src/client/content.js.map +1 -1
- package/dist/src/client/hooks.d.ts +4 -0
- package/dist/src/client/hooks.d.ts.map +1 -1
- package/dist/src/client/hooks.js +115 -28
- package/dist/src/client/hooks.js.map +1 -1
- package/dist/src/client/index.d.ts +1 -0
- package/dist/src/client/index.d.ts.map +1 -1
- package/dist/src/client/index.js +1 -0
- package/dist/src/client/index.js.map +1 -1
- package/dist/src/client/types.d.ts +2 -2
- package/dist/src/client/types.d.ts.map +1 -1
- package/dist/src/content-upload.d.ts +9 -10
- package/dist/src/content-upload.d.ts.map +1 -1
- package/dist/src/content-upload.js +8 -4
- package/dist/src/content-upload.js.map +1 -1
- package/dist/src/draft/generated.d.ts +73 -47
- package/dist/src/draft/generated.d.ts.map +1 -1
- package/dist/src/draft/generated.js +502 -251
- package/dist/src/draft/generated.js.map +1 -1
- package/dist/src/draft/index.d.ts +4 -1
- package/dist/src/draft/index.d.ts.map +1 -1
- package/dist/src/draft/index.js +2 -0
- package/dist/src/draft/index.js.map +1 -1
- package/dist/src/draft/schemas.d.ts +112 -40
- package/dist/src/draft/schemas.d.ts.map +1 -1
- package/dist/src/draft/schemas.js +158 -120
- package/dist/src/draft/schemas.js.map +1 -1
- package/dist/src/draft-runtime.d.ts +5 -5
- package/dist/src/draft-runtime.d.ts.map +1 -1
- package/dist/src/draft-runtime.js +3 -5
- package/dist/src/draft-runtime.js.map +1 -1
- package/dist/src/server/attachments.d.ts +2 -2
- package/dist/src/server/attachments.d.ts.map +1 -1
- package/dist/src/server/attachments.js +3 -3
- package/dist/src/server/attachments.js.map +1 -1
- package/dist/src/server/hooks.d.ts.map +1 -1
- package/dist/src/server/hooks.js +4 -2
- package/dist/src/server/hooks.js.map +1 -1
- package/dist/src/server/index.d.ts +3 -1
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +1 -0
- package/dist/src/server/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/composition.ts +15 -6
- package/src/client/content.ts +19 -7
- package/src/client/hooks.ts +138 -29
- package/src/client/index.ts +2 -0
- package/src/client/types.ts +2 -2
- package/src/content-upload.ts +14 -13
- package/src/draft/ahp-codegen.lock.json +14 -6
- package/src/draft/generated.ts +733 -427
- package/src/draft/index.ts +6 -1
- package/src/draft/schemas.ts +160 -120
- package/src/draft-runtime.ts +7 -9
- package/src/server/attachments.ts +5 -5
- package/src/server/hooks.ts +3 -2
- package/src/server/index.ts +5 -0
package/src/client/hooks.ts
CHANGED
|
@@ -107,6 +107,12 @@ const matches = (selector: string, type: string) =>
|
|
|
107
107
|
selector === type ||
|
|
108
108
|
(selector.endsWith(".*") && selector.slice(0, -2) === type.split(".")[0]);
|
|
109
109
|
|
|
110
|
+
type ElicitationLifetime = {
|
|
111
|
+
id: string;
|
|
112
|
+
sessionId: string | undefined;
|
|
113
|
+
retired: boolean;
|
|
114
|
+
};
|
|
115
|
+
|
|
110
116
|
/** Configuration-driven, harness-facing AHP client. No host operation is executed. */
|
|
111
117
|
export class Hooks {
|
|
112
118
|
readonly initialized: Promise<void>;
|
|
@@ -124,6 +130,9 @@ export class Hooks {
|
|
|
124
130
|
{ event: any; bytes: Uint8Array }
|
|
125
131
|
>();
|
|
126
132
|
private elicitationBytes = 0;
|
|
133
|
+
// Tokens exist only for active calls, including preparation before any bytes
|
|
134
|
+
// can be retained. Retirement invalidates those calls without tombstones.
|
|
135
|
+
private readonly activeElicitations = new Set<ElicitationLifetime>();
|
|
127
136
|
|
|
128
137
|
constructor(config: unknown, options: HooksOptions) {
|
|
129
138
|
this.options = {
|
|
@@ -435,16 +444,40 @@ export class Hooks {
|
|
|
435
444
|
manager.own(options.contentSources);
|
|
436
445
|
this.managers.add(manager);
|
|
437
446
|
const signal = combineSignals(this.lifetime.signal, options.signal);
|
|
447
|
+
const exchange: ElicitationLifetime | undefined =
|
|
448
|
+
type === "user.elicitation.request"
|
|
449
|
+
? {
|
|
450
|
+
id: input.id ?? randomUUID(),
|
|
451
|
+
sessionId: (input as any).session?.id,
|
|
452
|
+
retired: false,
|
|
453
|
+
}
|
|
454
|
+
: undefined;
|
|
455
|
+
if (exchange) this.activeElicitations.add(exchange);
|
|
456
|
+
let completed = false;
|
|
438
457
|
const work = (async () => {
|
|
439
458
|
// Calls after close are misuse. Active calls return interruption evidence.
|
|
440
459
|
this.lifetime.signal.throwIfAborted();
|
|
441
460
|
await this.initialized;
|
|
442
|
-
return this.run(type, input, options, signal, manager);
|
|
461
|
+
return this.run(type, input, options, signal, manager, exchange);
|
|
443
462
|
})();
|
|
444
463
|
this.pending.add(work);
|
|
445
464
|
try {
|
|
446
|
-
|
|
465
|
+
const result = await work;
|
|
466
|
+
completed = true;
|
|
467
|
+
return result;
|
|
447
468
|
} finally {
|
|
469
|
+
// Terminal exchanges retire even when validation or cancellation bypasses run.
|
|
470
|
+
if (type === "user.elicitation.result")
|
|
471
|
+
this.forgetElicitation((input as any).parentEventId);
|
|
472
|
+
if (
|
|
473
|
+
type === "user.elicitation.request" &&
|
|
474
|
+
(!completed || signal.aborted) &&
|
|
475
|
+
input.id
|
|
476
|
+
)
|
|
477
|
+
this.forgetElicitation(input.id);
|
|
478
|
+
if (type === "session.end")
|
|
479
|
+
this.retireSessionElicitations((input as any).session?.id);
|
|
480
|
+
if (exchange) this.activeElicitations.delete(exchange);
|
|
448
481
|
await manager.close();
|
|
449
482
|
this.managers.delete(manager);
|
|
450
483
|
this.pending.delete(work);
|
|
@@ -457,12 +490,13 @@ export class Hooks {
|
|
|
457
490
|
options: BoundaryOptions,
|
|
458
491
|
signal: AbortSignal,
|
|
459
492
|
manager: ContentManager,
|
|
493
|
+
exchange?: ElicitationLifetime,
|
|
460
494
|
): Promise<BoundaryResult<K>> {
|
|
461
495
|
let event: any = cloneInput({
|
|
462
496
|
...input,
|
|
463
497
|
type,
|
|
464
498
|
source: this.options.source,
|
|
465
|
-
id: input.id ?? randomUUID(),
|
|
499
|
+
id: exchange?.id ?? input.id ?? randomUUID(),
|
|
466
500
|
time: input.time ?? new Date().toISOString(),
|
|
467
501
|
});
|
|
468
502
|
bindContentSources(type, event, options.contentSources);
|
|
@@ -494,7 +528,7 @@ export class Hooks {
|
|
|
494
528
|
};
|
|
495
529
|
if (signal.aborted)
|
|
496
530
|
return {
|
|
497
|
-
event,
|
|
531
|
+
event: await resultEvent(event, manager),
|
|
498
532
|
response: {
|
|
499
533
|
jsonrpc: "2.0",
|
|
500
534
|
id: requestId,
|
|
@@ -512,7 +546,9 @@ export class Hooks {
|
|
|
512
546
|
observations: Promise.resolve([]),
|
|
513
547
|
interrupted: true,
|
|
514
548
|
} as BoundaryResult<K>;
|
|
549
|
+
const localSources = new Set<ReadableStream<Uint8Array>>();
|
|
515
550
|
const originalRequest = this.elicitationFor(event);
|
|
551
|
+
manager.own(originalRequest);
|
|
516
552
|
let effects: Effect[] = [];
|
|
517
553
|
let state = structuredClone(options.initialState);
|
|
518
554
|
// A supplied snapshot is input to the first receiver, not a newly accepted
|
|
@@ -636,7 +672,7 @@ export class Hooks {
|
|
|
636
672
|
event.elicitation.request.body,
|
|
637
673
|
signal,
|
|
638
674
|
);
|
|
639
|
-
this.rememberElicitation(event, bytes);
|
|
675
|
+
this.rememberElicitation(event, bytes, exchange!);
|
|
640
676
|
}
|
|
641
677
|
const request = {
|
|
642
678
|
jsonrpc: "2.0",
|
|
@@ -692,6 +728,10 @@ export class Hooks {
|
|
|
692
728
|
const staged = await raceAbort(
|
|
693
729
|
composeResponseAsync(event, effects, decoded.value, caps, {
|
|
694
730
|
readContent: (body) => manager.readBody(body, deadline),
|
|
731
|
+
ownContent: (body) => {
|
|
732
|
+
manager.own(body);
|
|
733
|
+
localSources.add(body);
|
|
734
|
+
},
|
|
695
735
|
selectedEvent: projected,
|
|
696
736
|
state,
|
|
697
737
|
...(originalRequest
|
|
@@ -771,40 +811,33 @@ export class Hooks {
|
|
|
771
811
|
const jobs = observations.map((route) =>
|
|
772
812
|
this.observe(route, snapshot, manager, signal),
|
|
773
813
|
);
|
|
774
|
-
observed = Promise.all(jobs)
|
|
775
|
-
.
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
)
|
|
780
|
-
.finally(async () => {
|
|
781
|
-
await manager.close();
|
|
782
|
-
this.managers.delete(manager);
|
|
783
|
-
});
|
|
814
|
+
observed = Promise.all(jobs).then((results) =>
|
|
815
|
+
results.filter(
|
|
816
|
+
(result): result is DeliveryError => result !== undefined,
|
|
817
|
+
),
|
|
818
|
+
);
|
|
784
819
|
await observed;
|
|
785
|
-
} else {
|
|
786
|
-
await manager.close();
|
|
787
|
-
this.managers.delete(manager);
|
|
788
820
|
}
|
|
789
821
|
interrupted ||= signal.aborted;
|
|
790
822
|
if (type === "user.elicitation.result")
|
|
791
823
|
this.forgetElicitation(event.parentEventId);
|
|
792
824
|
if (
|
|
793
825
|
type === "user.elicitation.request" &&
|
|
794
|
-
(interrupted ||
|
|
826
|
+
(interrupted ||
|
|
827
|
+
errors.length > 0 ||
|
|
828
|
+
(await observed).length > 0 ||
|
|
829
|
+
effects.some((e) => e.type === "deny"))
|
|
795
830
|
)
|
|
796
831
|
this.forgetElicitation(event.id);
|
|
797
832
|
if (type === "session.end")
|
|
798
|
-
|
|
799
|
-
if (entry.event.session?.id === event.session?.id)
|
|
800
|
-
this.forgetElicitation(id);
|
|
833
|
+
this.retireSessionElicitations(event.session?.id);
|
|
801
834
|
const response = {
|
|
802
835
|
jsonrpc: "2.0",
|
|
803
836
|
id: requestId,
|
|
804
837
|
result: { protocolVersion: "draft", effects },
|
|
805
838
|
} as InterceptResponse;
|
|
806
839
|
return {
|
|
807
|
-
event,
|
|
840
|
+
event: await resultEvent(event, manager, localSources),
|
|
808
841
|
response,
|
|
809
842
|
state: snapshotState(state),
|
|
810
843
|
get permission() {
|
|
@@ -819,6 +852,8 @@ export class Hooks {
|
|
|
819
852
|
interrupted,
|
|
820
853
|
} as BoundaryResult<K>;
|
|
821
854
|
} catch (error) {
|
|
855
|
+
if (type === "user.elicitation.request")
|
|
856
|
+
this.forgetElicitation(requestId);
|
|
822
857
|
await manager.close();
|
|
823
858
|
this.managers.delete(manager);
|
|
824
859
|
throw error;
|
|
@@ -887,18 +922,33 @@ export class Hooks {
|
|
|
887
922
|
}
|
|
888
923
|
}
|
|
889
924
|
|
|
890
|
-
private rememberElicitation(
|
|
925
|
+
private rememberElicitation(
|
|
926
|
+
event: any,
|
|
927
|
+
bytes: Uint8Array,
|
|
928
|
+
exchange: ElicitationLifetime,
|
|
929
|
+
): void {
|
|
930
|
+
if (exchange.retired || this.lifetime.signal.aborted) return;
|
|
891
931
|
if (this.elicitations.has(event.id)) return;
|
|
892
932
|
const limit = this.options.maxContentBytes ?? 64 * 1024 * 1024;
|
|
893
933
|
if (bytes.byteLength > limit)
|
|
894
934
|
throw new Error("Elicitation request exceeds retention budget");
|
|
895
|
-
|
|
935
|
+
if (
|
|
896
936
|
this.elicitationBytes + bytes.byteLength > limit ||
|
|
897
937
|
this.elicitations.size >= 128
|
|
898
938
|
)
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
939
|
+
throw new Error("Active elicitation exchanges exceed retention budget");
|
|
940
|
+
// Pending exchanges own only correlation facts and selected request bytes.
|
|
941
|
+
const stored = {
|
|
942
|
+
type: event.type,
|
|
943
|
+
id: event.id,
|
|
944
|
+
source: event.source,
|
|
945
|
+
session: event.session ? { id: event.session.id } : undefined,
|
|
946
|
+
elicitation: {
|
|
947
|
+
mode: event.elicitation.mode,
|
|
948
|
+
server: event.elicitation.server,
|
|
949
|
+
request: { mediaType: event.elicitation.request.mediaType },
|
|
950
|
+
},
|
|
951
|
+
};
|
|
902
952
|
this.elicitations.set(event.id, { event: stored, bytes });
|
|
903
953
|
this.elicitationBytes += bytes.byteLength;
|
|
904
954
|
}
|
|
@@ -916,7 +966,21 @@ export class Hooks {
|
|
|
916
966
|
});
|
|
917
967
|
return copy;
|
|
918
968
|
}
|
|
969
|
+
/** End an abandoned host elicitation exchange without delivering a result. */
|
|
970
|
+
discardElicitation(requestEventId: string): void {
|
|
971
|
+
this.forgetElicitation(requestEventId);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
private retireSessionElicitations(sessionId: string | undefined): void {
|
|
975
|
+
for (const exchange of this.activeElicitations)
|
|
976
|
+
if (exchange.sessionId === sessionId) exchange.retired = true;
|
|
977
|
+
for (const [id, entry] of this.elicitations)
|
|
978
|
+
if (entry.event.session?.id === sessionId) this.forgetElicitation(id);
|
|
979
|
+
}
|
|
980
|
+
|
|
919
981
|
private forgetElicitation(id: string): void {
|
|
982
|
+
for (const exchange of this.activeElicitations)
|
|
983
|
+
if (exchange.id === id) exchange.retired = true;
|
|
920
984
|
const entry = this.elicitations.get(id);
|
|
921
985
|
if (entry) this.elicitationBytes -= entry.bytes.byteLength;
|
|
922
986
|
this.elicitations.delete(id);
|
|
@@ -925,6 +989,7 @@ export class Hooks {
|
|
|
925
989
|
/** Cancel pending work and release SDK-owned resources; injected stores remain owned by the host. */
|
|
926
990
|
close(): Promise<void> {
|
|
927
991
|
if (!this.closed) {
|
|
992
|
+
for (const exchange of this.activeElicitations) exchange.retired = true;
|
|
928
993
|
this.lifetime.abort(new Error("Hooks closed"));
|
|
929
994
|
this.closed = (async () => {
|
|
930
995
|
await this.initialized.catch(() => {});
|
|
@@ -1150,6 +1215,48 @@ export class Hooks {
|
|
|
1150
1215
|
}
|
|
1151
1216
|
}
|
|
1152
1217
|
|
|
1218
|
+
// Return independent copies of prepared or synthesized payloads. Never read an
|
|
1219
|
+
// unselected producer stream solely to construct a result.
|
|
1220
|
+
async function resultEvent(
|
|
1221
|
+
value: any,
|
|
1222
|
+
manager: ContentManager,
|
|
1223
|
+
localSources = new Set<ReadableStream<Uint8Array>>(),
|
|
1224
|
+
): Promise<any> {
|
|
1225
|
+
if (value instanceof ContentSource) value = value.stream;
|
|
1226
|
+
if (value instanceof ReadableStream) {
|
|
1227
|
+
const bytes = localSources.has(value)
|
|
1228
|
+
? await manager.readBody(value)
|
|
1229
|
+
: await manager.copySnapshot(value);
|
|
1230
|
+
if (!bytes) return undefined;
|
|
1231
|
+
return new ReadableStream<Uint8Array>({
|
|
1232
|
+
start(controller) {
|
|
1233
|
+
controller.enqueue(bytes);
|
|
1234
|
+
controller.close();
|
|
1235
|
+
},
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
if (Array.isArray(value))
|
|
1239
|
+
return Promise.all(
|
|
1240
|
+
value.map((child) => resultEvent(child, manager, localSources)),
|
|
1241
|
+
);
|
|
1242
|
+
if (value && typeof value === "object") {
|
|
1243
|
+
const entries = await Promise.all(
|
|
1244
|
+
Object.entries(value).map(async ([key, child]) =>
|
|
1245
|
+
[key, await resultEvent(child, manager, localSources)] as const),
|
|
1246
|
+
);
|
|
1247
|
+
const copy = Object.fromEntries(
|
|
1248
|
+
entries.filter(([, child]) => child !== undefined),
|
|
1249
|
+
);
|
|
1250
|
+
if (
|
|
1251
|
+
(value.body instanceof ReadableStream || value.body instanceof ContentSource) &&
|
|
1252
|
+
!copy.body
|
|
1253
|
+
)
|
|
1254
|
+
copy.selection = "metadata";
|
|
1255
|
+
return copy;
|
|
1256
|
+
}
|
|
1257
|
+
return value;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1153
1260
|
function cloneInput(value: any): any {
|
|
1154
1261
|
if (value instanceof ContentSource) return value.stream;
|
|
1155
1262
|
if (value instanceof ReadableStream) return value;
|
|
@@ -1217,13 +1324,15 @@ function combineSignals(first: AbortSignal, second?: AbortSignal): AbortSignal {
|
|
|
1217
1324
|
return second ? AbortSignal.any([first, second]) : first;
|
|
1218
1325
|
}
|
|
1219
1326
|
function raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {
|
|
1220
|
-
if (signal.aborted) return Promise.reject(signal.reason);
|
|
1221
1327
|
return new Promise((resolve, reject) => {
|
|
1222
1328
|
const abort = () => reject(signal.reason);
|
|
1223
1329
|
signal.addEventListener("abort", abort, { once: true });
|
|
1224
1330
|
promise
|
|
1225
1331
|
.then(resolve, reject)
|
|
1226
1332
|
.finally(() => signal.removeEventListener("abort", abort));
|
|
1333
|
+
// The operation already exists: own its rejection even if cancellation
|
|
1334
|
+
// occurred synchronously while constructing it.
|
|
1335
|
+
if (signal.aborted) abort();
|
|
1227
1336
|
});
|
|
1228
1337
|
}
|
|
1229
1338
|
|
package/src/client/index.ts
CHANGED
package/src/client/types.ts
CHANGED
|
@@ -12,12 +12,12 @@ import type {
|
|
|
12
12
|
ExecutionEventContextCompactBefore,
|
|
13
13
|
InterceptResponse,
|
|
14
14
|
InterceptRequest,
|
|
15
|
-
|
|
15
|
+
Event,
|
|
16
16
|
StaticCapabilityManifest,
|
|
17
17
|
} from "../draft/generated.js";
|
|
18
18
|
import type { AuthProvider, DeliveryAuthProvider } from "./auth.js";
|
|
19
19
|
|
|
20
|
-
export type Event
|
|
20
|
+
export type { Event } from "../draft/generated.js";
|
|
21
21
|
export type { EventType } from "../draft/generated.js";
|
|
22
22
|
/** Remove generated JSON extension index signatures while preserving named fields. */
|
|
23
23
|
type Fields<T> = {
|
package/src/content-upload.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ContentReference, ContentUploadReceipt } from "./draft/generated.js";
|
|
2
|
+
export type { ContentReference, ContentUploadReceipt } from "./draft/generated.js";
|
|
1
3
|
/** Normative HTTP content upload. Event authentication is deliberately not an input. */
|
|
2
4
|
export interface UploadConfiguration {
|
|
3
5
|
endpoint: string;
|
|
@@ -6,17 +8,16 @@ export interface UploadConfiguration {
|
|
|
6
8
|
maxBytes?: number;
|
|
7
9
|
[key: string]: unknown;
|
|
8
10
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
sha256: string;
|
|
11
|
+
/** Explicitly discard upload confirmation metadata before publishing a reference. */
|
|
12
|
+
export function contentReference(receipt: ContentUploadReceipt): ContentReference {
|
|
13
|
+
return { ref: receipt.ref };
|
|
13
14
|
}
|
|
14
|
-
/**
|
|
15
|
-
export function
|
|
15
|
+
/** Verify the upload receipt against the exact sent bytes before publication. */
|
|
16
|
+
export function validateUploadReceipt(
|
|
16
17
|
value: unknown,
|
|
17
18
|
size: number,
|
|
18
19
|
sha256: string,
|
|
19
|
-
):
|
|
20
|
+
): ContentUploadReceipt {
|
|
20
21
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
21
22
|
throw Error("Invalid upload descriptor");
|
|
22
23
|
const body = value as Record<string, unknown>;
|
|
@@ -40,7 +41,7 @@ export async function uploadContent(
|
|
|
40
41
|
resolveToken?: (name: string) => string | undefined;
|
|
41
42
|
allowLoopback?: boolean;
|
|
42
43
|
} = {},
|
|
43
|
-
): Promise<
|
|
44
|
+
): Promise<ContentUploadReceipt> {
|
|
44
45
|
if (
|
|
45
46
|
!config ||
|
|
46
47
|
typeof config.endpoint !== "string" ||
|
|
@@ -107,7 +108,7 @@ export async function uploadContent(
|
|
|
107
108
|
await response.body?.cancel();
|
|
108
109
|
throw Error(`Content upload failed: HTTP ${response.status}`);
|
|
109
110
|
}
|
|
110
|
-
return
|
|
111
|
+
return validateUploadReceipt(await response.json(), snapshot.length, sha256);
|
|
111
112
|
}
|
|
112
113
|
export interface NormalizedContentInput {
|
|
113
114
|
id: string;
|
|
@@ -129,7 +130,7 @@ export interface NormalizedContentView {
|
|
|
129
130
|
parentItemId?: string;
|
|
130
131
|
synthesized?: boolean;
|
|
131
132
|
selection: "body" | "metadata" | "omit";
|
|
132
|
-
body?:
|
|
133
|
+
body?: ContentReference;
|
|
133
134
|
gap?: { reason: string };
|
|
134
135
|
}
|
|
135
136
|
/** Select once per subscriber. Return no body reference until upload confirms availability. */
|
|
@@ -139,7 +140,7 @@ export async function prepareWireContent(
|
|
|
139
140
|
subscription?: string;
|
|
140
141
|
selection: Record<string, "body" | "metadata" | "omit">;
|
|
141
142
|
authorized: (item: NormalizedContentInput) => boolean;
|
|
142
|
-
upload: (bytes: Uint8Array) => Promise<
|
|
143
|
+
upload: (bytes: Uint8Array) => Promise<ContentUploadReceipt>;
|
|
143
144
|
mode: "intercept" | "observe";
|
|
144
145
|
failClosed: boolean;
|
|
145
146
|
timeoutMs?: number;
|
|
@@ -174,7 +175,7 @@ export async function prepareWireContent(
|
|
|
174
175
|
try {
|
|
175
176
|
const bytes = new Uint8Array(item.bytes);
|
|
176
177
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
177
|
-
let body:
|
|
178
|
+
let body: ContentUploadReceipt;
|
|
178
179
|
try {
|
|
179
180
|
body = await Promise.race([
|
|
180
181
|
options.upload(bytes.slice()),
|
|
@@ -192,7 +193,7 @@ export async function prepareWireContent(
|
|
|
192
193
|
new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)),
|
|
193
194
|
(byte) => byte.toString(16).padStart(2, "0"),
|
|
194
195
|
).join("");
|
|
195
|
-
view.body =
|
|
196
|
+
view.body = contentReference(validateUploadReceipt(body, bytes.length, hash));
|
|
196
197
|
} catch {
|
|
197
198
|
view.gap = { reason: "transfer_failed" };
|
|
198
199
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sourceRepository": "agenthooksprotocol/agent-hooks-protocol",
|
|
3
|
-
"sourceCommit": "
|
|
3
|
+
"sourceCommit": "05b4750cb2dccf17d75236182d6971f3cbe97e1f",
|
|
4
4
|
"schemaRevision": "draft",
|
|
5
5
|
"protocolVersion": "draft",
|
|
6
6
|
"generatorVersion": "0.1.0",
|
|
7
7
|
"language": "typescript",
|
|
8
|
-
"schemaManifestSha256": "
|
|
8
|
+
"schemaManifestSha256": "b397a3c4d613a67396f1727190052ce2f3bb208cdfa03e066f0c045525f25451",
|
|
9
9
|
"documents": [
|
|
10
10
|
{
|
|
11
11
|
"path": "schema/draft/capabilities-request.schema.json",
|
|
@@ -29,16 +29,20 @@
|
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "schema/draft/content-item.schema.json",
|
|
32
|
-
"sha256": "
|
|
32
|
+
"sha256": "21ce30d451d6fe20eb56cf09108fe33500e40839669d2922911dd9b0b8cfe762"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"path": "schema/draft/content-reference.schema.json",
|
|
36
|
-
"sha256": "
|
|
36
|
+
"sha256": "32b345818648f2d28dd0175705a85b22569ea535867349262e055bb3e3999e87"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"path": "schema/draft/content-selection.schema.json",
|
|
40
40
|
"sha256": "fe57bce4a7a6c7a8d51728634349621ba327b5ef118bc60b1c330260392e2a62"
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
"path": "schema/draft/content-upload-receipt.schema.json",
|
|
44
|
+
"sha256": "02e04375b2e4764eea0e70299a9ae29f0d49b407dca797f49299a3cb2d7613dc"
|
|
45
|
+
},
|
|
42
46
|
{
|
|
43
47
|
"path": "schema/draft/content-upload.schema.json",
|
|
44
48
|
"sha256": "a405adb54b8de11f3814840f7cbcd54d76cdcc2e25d00942f1c1755eb05c6753"
|
|
@@ -51,6 +55,10 @@
|
|
|
51
55
|
"path": "schema/draft/effect.schema.json",
|
|
52
56
|
"sha256": "1448151926563244194ed56b15e3074ddfc8ec8c10889f2e42ab49d66be2d031"
|
|
53
57
|
},
|
|
58
|
+
{
|
|
59
|
+
"path": "schema/draft/event.schema.json",
|
|
60
|
+
"sha256": "7b19aa60dc775b23b74d78aad2cdc634e07c454c68f1a60b125c3e4ff391f1f9"
|
|
61
|
+
},
|
|
54
62
|
{
|
|
55
63
|
"path": "schema/draft/execution-event.schema.json",
|
|
56
64
|
"sha256": "d9b13cf28e20eced629cfe8eb822eb6dfb2ac453fe14e6d2334c2f4b2793512a"
|
|
@@ -73,7 +81,7 @@
|
|
|
73
81
|
},
|
|
74
82
|
{
|
|
75
83
|
"path": "schema/draft/intercept-request.schema.json",
|
|
76
|
-
"sha256": "
|
|
84
|
+
"sha256": "179c6afd7092bcb1fa91522e6fbe6f63aa28d5dfbef35c9aa661304028ba48c9"
|
|
77
85
|
},
|
|
78
86
|
{
|
|
79
87
|
"path": "schema/draft/intercept-response.schema.json",
|
|
@@ -89,7 +97,7 @@
|
|
|
89
97
|
},
|
|
90
98
|
{
|
|
91
99
|
"path": "schema/draft/observe-notification.schema.json",
|
|
92
|
-
"sha256": "
|
|
100
|
+
"sha256": "770dd91480b4a0a0e8a3565e3724ab5d77bbede19faff223e10a8e442f926a62"
|
|
93
101
|
},
|
|
94
102
|
{
|
|
95
103
|
"path": "schema/draft/registration.schema.json",
|