@evomap/evolver-adapter-public 2.0.0-beta.16 → 2.0.0-beta.17
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
CHANGED
|
@@ -15,6 +15,7 @@ export * from './offlinePermit.js';
|
|
|
15
15
|
export * from './hubReuse.js';
|
|
16
16
|
export * from './hubUrl.js';
|
|
17
17
|
export * from './learningPacketSink.js';
|
|
18
|
+
export * from './learningPacketFeedback.js';
|
|
18
19
|
export * from './atp.js';
|
|
19
20
|
export * from './pricing/modelPrices.js';
|
|
20
21
|
export * from './connect.js';
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from './offlinePermit.js';
|
|
|
15
15
|
export * from './hubReuse.js';
|
|
16
16
|
export * from './hubUrl.js';
|
|
17
17
|
export * from './learningPacketSink.js';
|
|
18
|
+
export * from './learningPacketFeedback.js';
|
|
18
19
|
export * from './atp.js';
|
|
19
20
|
export * from './pricing/modelPrices.js';
|
|
20
21
|
export * from './connect.js';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { hub } from '@evomap/evolver-core';
|
|
2
|
+
import { type FetchLike } from './hubFetch.js';
|
|
3
|
+
/** Hub appendLearningFeedbackSchema closed enums (evomap-hub src/schemas/learningOps.js). */
|
|
4
|
+
export declare const LEARNING_FEEDBACK_TYPES: readonly ["outcome", "rating", "correction", "governance", "note"];
|
|
5
|
+
export type LearningFeedbackType = (typeof LEARNING_FEEDBACK_TYPES)[number];
|
|
6
|
+
export declare const LEARNING_FEEDBACK_DECISIONS: readonly ["accepted", "rejected", "needs_redaction", "not_training_eligible", "training_candidate", "note"];
|
|
7
|
+
export type LearningFeedbackDecision = (typeof LEARNING_FEEDBACK_DECISIONS)[number];
|
|
8
|
+
export interface LearningPacketFeedbackInput {
|
|
9
|
+
/** Default hub-side: 'outcome'. */
|
|
10
|
+
feedbackType?: LearningFeedbackType;
|
|
11
|
+
decision: LearningFeedbackDecision;
|
|
12
|
+
/** 0..1 (hub-validated). */
|
|
13
|
+
rating?: number;
|
|
14
|
+
scores?: Record<string, unknown>;
|
|
15
|
+
rationale?: string;
|
|
16
|
+
/** Hub VERIFIERS enum member (e.g. 'automated_test', 'human'). */
|
|
17
|
+
verifier?: string;
|
|
18
|
+
/** Hub FAILURE_CATEGORIES enum member. */
|
|
19
|
+
failureCategory?: string;
|
|
20
|
+
/** Anchor the feedback to one trace event instead of the whole packet. */
|
|
21
|
+
traceEventId?: string;
|
|
22
|
+
actorNodeId?: string;
|
|
23
|
+
}
|
|
24
|
+
export type LearningFeedbackResult = {
|
|
25
|
+
ok: true;
|
|
26
|
+
feedbackId?: string;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
reason: string;
|
|
30
|
+
};
|
|
31
|
+
/** Server-managed governance/eligibility state read back from GET /api/learning-packets/:id. */
|
|
32
|
+
export interface LearningPacketStatus {
|
|
33
|
+
id: string;
|
|
34
|
+
status?: string;
|
|
35
|
+
outcomeStatus?: string | null;
|
|
36
|
+
verifier?: string | null;
|
|
37
|
+
/** LearningOpsTrainingEligibility mirror: pending/eligible/ineligible/revoked/expired. */
|
|
38
|
+
trainingEligibilityStatus?: string | null;
|
|
39
|
+
/** pending/approved/blocked/purge_requested. */
|
|
40
|
+
governanceStatus?: string | null;
|
|
41
|
+
trainingEligible?: boolean;
|
|
42
|
+
consentStatus?: string | null;
|
|
43
|
+
redactionStatus?: string | null;
|
|
44
|
+
retentionPolicy?: string | null;
|
|
45
|
+
}
|
|
46
|
+
export type LearningPacketReadResult = {
|
|
47
|
+
ok: true;
|
|
48
|
+
packet: LearningPacketStatus;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
reason: string;
|
|
52
|
+
};
|
|
53
|
+
export interface HubLearningPacketFeedbackClientOptions {
|
|
54
|
+
baseUrl: string;
|
|
55
|
+
auth: hub.AuthProvider;
|
|
56
|
+
fetchFn: FetchLike;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Feedback append + packet governance read-back against the hub Learning Ops API. Best-effort by the
|
|
60
|
+
* same contract as HubLearningPacketSink: this is observability/ops tooling, so every failure —
|
|
61
|
+
* network, auth, 4xx/5xx, unparseable body — returns { ok:false, reason } and never throws.
|
|
62
|
+
*/
|
|
63
|
+
export declare class HubLearningPacketFeedbackClient {
|
|
64
|
+
private readonly opts;
|
|
65
|
+
constructor(opts: HubLearningPacketFeedbackClientOptions);
|
|
66
|
+
submitFeedback(packetId: string, feedback: LearningPacketFeedbackInput): Promise<LearningFeedbackResult>;
|
|
67
|
+
getPacket(packetId: string): Promise<LearningPacketReadResult>;
|
|
68
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { assertHubUrlSecure, isHubUnreachableError } from './hubFetch.js';
|
|
2
|
+
import { learningOpsAuthHeaders } from './learningPacketSink.js';
|
|
3
|
+
/** Hub appendLearningFeedbackSchema closed enums (evomap-hub src/schemas/learningOps.js). */
|
|
4
|
+
export const LEARNING_FEEDBACK_TYPES = ['outcome', 'rating', 'correction', 'governance', 'note'];
|
|
5
|
+
export const LEARNING_FEEDBACK_DECISIONS = [
|
|
6
|
+
'accepted', 'rejected', 'needs_redaction', 'not_training_eligible', 'training_candidate', 'note',
|
|
7
|
+
];
|
|
8
|
+
function pickString(record, key) {
|
|
9
|
+
const value = record[key];
|
|
10
|
+
if (value === null)
|
|
11
|
+
return null;
|
|
12
|
+
return typeof value === 'string' ? value : undefined;
|
|
13
|
+
}
|
|
14
|
+
async function failureReason(res) {
|
|
15
|
+
const text = await res.text().catch(() => '');
|
|
16
|
+
return `hub ${res.status}${text ? `: ${text.slice(0, 200)}` : ''}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Feedback append + packet governance read-back against the hub Learning Ops API. Best-effort by the
|
|
20
|
+
* same contract as HubLearningPacketSink: this is observability/ops tooling, so every failure —
|
|
21
|
+
* network, auth, 4xx/5xx, unparseable body — returns { ok:false, reason } and never throws.
|
|
22
|
+
*/
|
|
23
|
+
export class HubLearningPacketFeedbackClient {
|
|
24
|
+
opts;
|
|
25
|
+
constructor(opts) {
|
|
26
|
+
this.opts = opts;
|
|
27
|
+
}
|
|
28
|
+
async submitFeedback(packetId, feedback) {
|
|
29
|
+
try {
|
|
30
|
+
const path = `/api/learning-packets/${encodeURIComponent(packetId)}/feedback`;
|
|
31
|
+
const url = `${this.opts.baseUrl}${path}`;
|
|
32
|
+
assertHubUrlSecure(url);
|
|
33
|
+
const headers = await learningOpsAuthHeaders(this.opts.auth, 'POST', path);
|
|
34
|
+
// Exactly the appendLearningFeedbackSchema fields (strict zod): optional keys are omitted, not nulled.
|
|
35
|
+
const res = await this.opts.fetchFn(url, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers,
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
decision: feedback.decision,
|
|
40
|
+
...(feedback.feedbackType !== undefined ? { feedbackType: feedback.feedbackType } : {}),
|
|
41
|
+
...(feedback.rating !== undefined ? { rating: feedback.rating } : {}),
|
|
42
|
+
...(feedback.scores !== undefined ? { scores: feedback.scores } : {}),
|
|
43
|
+
...(feedback.rationale !== undefined ? { rationale: feedback.rationale } : {}),
|
|
44
|
+
...(feedback.verifier !== undefined ? { verifier: feedback.verifier } : {}),
|
|
45
|
+
...(feedback.failureCategory !== undefined ? { failureCategory: feedback.failureCategory } : {}),
|
|
46
|
+
...(feedback.traceEventId !== undefined ? { traceEventId: feedback.traceEventId } : {}),
|
|
47
|
+
...(feedback.actorNodeId !== undefined ? { actorNodeId: feedback.actorNodeId } : {}),
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
if (res.status === 201) {
|
|
51
|
+
const body = await res.json().catch(() => null);
|
|
52
|
+
const row = body && typeof body === 'object' ? body.feedback : undefined;
|
|
53
|
+
return { ok: true, ...(typeof row?.id === 'string' ? { feedbackId: row.id } : {}) };
|
|
54
|
+
}
|
|
55
|
+
return { ok: false, reason: await failureReason(res) };
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
if (isHubUnreachableError(e))
|
|
59
|
+
return { ok: false, reason: 'hub_unreachable' };
|
|
60
|
+
return { ok: false, reason: e instanceof Error ? e.message : String(e) };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async getPacket(packetId) {
|
|
64
|
+
try {
|
|
65
|
+
const path = `/api/learning-packets/${encodeURIComponent(packetId)}`;
|
|
66
|
+
const url = `${this.opts.baseUrl}${path}`;
|
|
67
|
+
assertHubUrlSecure(url);
|
|
68
|
+
const headers = await learningOpsAuthHeaders(this.opts.auth, 'GET', path);
|
|
69
|
+
const res = await this.opts.fetchFn(url, { method: 'GET', headers });
|
|
70
|
+
if (res.status !== 200)
|
|
71
|
+
return { ok: false, reason: await failureReason(res) };
|
|
72
|
+
const body = await res.json().catch(() => null);
|
|
73
|
+
const packet = body && typeof body === 'object' ? body.packet : undefined;
|
|
74
|
+
if (!packet || typeof packet !== 'object' || Array.isArray(packet)) {
|
|
75
|
+
return { ok: false, reason: 'hub 200: response missing packet object' };
|
|
76
|
+
}
|
|
77
|
+
const record = packet;
|
|
78
|
+
if (typeof record['id'] !== 'string' || record['id'].length === 0) {
|
|
79
|
+
return { ok: false, reason: 'hub 200: packet missing id' };
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
ok: true,
|
|
83
|
+
packet: {
|
|
84
|
+
id: record['id'],
|
|
85
|
+
...(pickString(record, 'status') !== undefined && pickString(record, 'status') !== null ? { status: record['status'] } : {}),
|
|
86
|
+
...(pickString(record, 'outcomeStatus') !== undefined ? { outcomeStatus: pickString(record, 'outcomeStatus') } : {}),
|
|
87
|
+
...(pickString(record, 'verifier') !== undefined ? { verifier: pickString(record, 'verifier') } : {}),
|
|
88
|
+
...(pickString(record, 'trainingEligibilityStatus') !== undefined ? { trainingEligibilityStatus: pickString(record, 'trainingEligibilityStatus') } : {}),
|
|
89
|
+
...(pickString(record, 'governanceStatus') !== undefined ? { governanceStatus: pickString(record, 'governanceStatus') } : {}),
|
|
90
|
+
...(typeof record['trainingEligible'] === 'boolean' ? { trainingEligible: record['trainingEligible'] } : {}),
|
|
91
|
+
...(pickString(record, 'consentStatus') !== undefined ? { consentStatus: pickString(record, 'consentStatus') } : {}),
|
|
92
|
+
...(pickString(record, 'redactionStatus') !== undefined ? { redactionStatus: pickString(record, 'redactionStatus') } : {}),
|
|
93
|
+
...(pickString(record, 'retentionPolicy') !== undefined ? { retentionPolicy: pickString(record, 'retentionPolicy') } : {}),
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
if (isHubUnreachableError(e))
|
|
99
|
+
return { ok: false, reason: 'hub_unreachable' };
|
|
100
|
+
return { ok: false, reason: e instanceof Error ? e.message : String(e) };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -11,6 +11,12 @@ export interface HubLearningPacketSinkOptions {
|
|
|
11
11
|
}
|
|
12
12
|
/** Deterministic content hash over the draft body (hub contentHash column, dedup aid). */
|
|
13
13
|
export declare function learningPacketContentHash(draft: trace.LearningPacketDraft): string;
|
|
14
|
+
/**
|
|
15
|
+
* Auth headers for the strict learning-packets routes (requireAuth reads Authorization only).
|
|
16
|
+
* A legacy body credential (bodyFields.node_secret) is promoted to Bearer — the strict schemas
|
|
17
|
+
* reject extra body fields, so credentials must never ride in the body here.
|
|
18
|
+
*/
|
|
19
|
+
export declare function learningOpsAuthHeaders(auth: hub.AuthProvider, method: string, path: string): Promise<Record<string, string>>;
|
|
14
20
|
/** Map a core draft to the hub createLearningPacket body. Exported for tests and for the private adapter to reuse. */
|
|
15
21
|
export declare function learningPacketWireBody(draft: trace.LearningPacketDraft, nodeId?: string): Record<string, unknown>;
|
|
16
22
|
/**
|
|
@@ -30,6 +30,19 @@ function outcomeStatusFor(status) {
|
|
|
30
30
|
export function learningPacketContentHash(draft) {
|
|
31
31
|
return `sha256:${createHash('sha256').update(JSON.stringify(draft)).digest('hex')}`;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Auth headers for the strict learning-packets routes (requireAuth reads Authorization only).
|
|
35
|
+
* A legacy body credential (bodyFields.node_secret) is promoted to Bearer — the strict schemas
|
|
36
|
+
* reject extra body fields, so credentials must never ride in the body here.
|
|
37
|
+
*/
|
|
38
|
+
export async function learningOpsAuthHeaders(auth, method, path) {
|
|
39
|
+
const signed = await auth.authenticate({ method, path });
|
|
40
|
+
const headers = { 'content-type': 'application/json', ...(signed.headers ?? {}) };
|
|
41
|
+
const bodySecret = signed.bodyFields?.['node_secret'];
|
|
42
|
+
if (headers['authorization'] === undefined && bodySecret !== undefined)
|
|
43
|
+
headers['authorization'] = `Bearer ${String(bodySecret)}`;
|
|
44
|
+
return headers;
|
|
45
|
+
}
|
|
33
46
|
/** Map a core draft to the hub createLearningPacket body. Exported for tests and for the private adapter to reuse. */
|
|
34
47
|
export function learningPacketWireBody(draft, nodeId) {
|
|
35
48
|
const truncated = draft.trajectory.length > HUB_TRACE_EVENTS_MAX;
|
|
@@ -48,6 +61,9 @@ export function learningPacketWireBody(draft, nodeId) {
|
|
|
48
61
|
contentHash: learningPacketContentHash(draft),
|
|
49
62
|
...(nodeId ? { nodeId } : {}),
|
|
50
63
|
...(outcomeStatus ? { outcomeStatus } : {}),
|
|
64
|
+
// evaluation fill-in (slice 6): a non-placeholder evaluation carries the runtime's external verifier
|
|
65
|
+
// ('automated_test' is in the hub VERIFIERS enum); passed/score details ride inside payload.evaluation.
|
|
66
|
+
...(draft.evaluation.verifier !== null ? { verifier: draft.evaluation.verifier } : {}),
|
|
51
67
|
...(failureCategory ? { failureCategory } : {}),
|
|
52
68
|
...(draft.task.summary !== null ? { summary: draft.task.summary } : {}),
|
|
53
69
|
payload: {
|
|
@@ -93,13 +109,7 @@ export class HubLearningPacketSink {
|
|
|
93
109
|
try {
|
|
94
110
|
const url = `${this.opts.baseUrl}/api/learning-packets`;
|
|
95
111
|
assertHubUrlSecure(url);
|
|
96
|
-
const
|
|
97
|
-
// The strict learning-packets schema rejects extra body fields, so a legacy body credential
|
|
98
|
-
// (bodyFields.node_secret) is promoted to Authorization: Bearer — the header requireAuth reads.
|
|
99
|
-
const headers = { 'content-type': 'application/json', ...(signed.headers ?? {}) };
|
|
100
|
-
const bodySecret = signed.bodyFields?.['node_secret'];
|
|
101
|
-
if (headers['authorization'] === undefined && bodySecret !== undefined)
|
|
102
|
-
headers['authorization'] = `Bearer ${String(bodySecret)}`;
|
|
112
|
+
const headers = await learningOpsAuthHeaders(this.opts.auth, 'POST', '/api/learning-packets');
|
|
103
113
|
const res = await this.opts.fetchFn(url, {
|
|
104
114
|
method: 'POST',
|
|
105
115
|
headers,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver-adapter-public",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.17",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "公版 hub 适配器 (积分/治理)",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@evomap/atp-sdk": "^0.1.0",
|
|
17
|
-
"@evomap/evolver-core": "2.0.0-beta.
|
|
17
|
+
"@evomap/evolver-core": "2.0.0-beta.17",
|
|
18
18
|
"undici": "^6.27.0"
|
|
19
19
|
},
|
|
20
20
|
"optionalDependencies": {
|