chore: establish local platform baseline

This commit is contained in:
Codex Local
2026-08-26 06:41:54 +08:00
commit 29bdf4900f
575 changed files with 95644 additions and 0 deletions
+467
View File
@@ -0,0 +1,467 @@
import type { components, paths } from "./schema";
type LivenessResponse = paths["/health/live"]["get"]["responses"]["200"]["content"]["application/json"];
type ReadinessResponse = paths["/health/ready"]["get"]["responses"]["200"]["content"]["application/json"];
type VersionResponse = paths["/api/v1/version"]["get"]["responses"]["200"]["content"]["application/json"];
type CurrentUserResponse = paths["/api/v1/me"]["get"]["responses"]["200"]["content"]["application/json"];
export type CurrentUser = components["schemas"]["CurrentUser"];
export type Household = components["schemas"]["Household"];
export type HouseholdMember = components["schemas"]["HouseholdMember"];
export type MemberInvitation = components["schemas"]["MemberInvitation"];
export type MemberInvitationPreview = components["schemas"]["MemberInvitationPreview"];
export type Subject = components["schemas"]["Subject"];
export type SubjectFact = components["schemas"]["SubjectFact"];
export type Consent = components["schemas"]["Consent"];
export type ConversationReadiness = components["schemas"]["ConversationReadiness"];
export type ManagedDevice = components["schemas"]["ManagedDevice"];
export type DevicePresence = components["schemas"]["DevicePresence"];
export type DeviceTwin = components["schemas"]["DeviceTwin"];
export type DeviceCommand = components["schemas"]["DeviceCommand"];
export type FirmwareSigningKey = components["schemas"]["FirmwareSigningKey"];
export type FirmwareRelease = components["schemas"]["FirmwareRelease"];
export type FirmwareReleaseImpact = components["schemas"]["FirmwareReleaseImpact"];
export type FirmwareRollout = components["schemas"]["FirmwareRollout"];
export type FirmwareAssignment = components["schemas"]["FirmwareAssignment"];
export type FirmwareAssignmentEvent = components["schemas"]["FirmwareAssignmentEvent"];
export type ConversationSummary = components["schemas"]["ConversationSummary"];
export type ConversationDetail = components["schemas"]["ConversationDetail"];
export type ConversationUsage = components["schemas"]["ConversationUsage"];
export type ConversationTurn = components["schemas"]["ConversationTurn"];
export type ConversationContentSummary = components["schemas"]["ConversationContentSummary"];
export type ConversationMedia = components["schemas"]["ConversationMedia"];
export type MediaSegment = components["schemas"]["MediaSegment"];
export type RecordingAccessGrant = components["schemas"]["RecordingAccessGrant"];
export type MediaAccess = components["schemas"]["MediaAccess"];
export type Story = components["schemas"]["Story"];
export type CuratedMemory = components["schemas"]["CuratedMemory"];
export type PrivacyRequest = components["schemas"]["PrivacyRequest"];
export type ExportTask = components["schemas"]["ExportTask"];
export type SharingGrant = components["schemas"]["SharingGrant"];
export type OpsConfigVersion = components["schemas"]["OpsConfigVersion"];
export type OpsConfigEvaluation = components["schemas"]["OpsConfigEvaluation"];
export type OpsResourceReference = components["schemas"]["OpsResourceReference"];
export type OpsDeviceGroup = components["schemas"]["OpsDeviceGroup"];
export type OpsConfigRollout = components["schemas"]["OpsConfigRollout"];
export type OpsConfigTransitionRequest = components["schemas"]["OpsConfigTransitionRequest"];
export type BillingPriceBook = components["schemas"]["BillingPriceBook"];
export type QuotaPolicy = components["schemas"]["QuotaPolicy"];
export type CostSummary = components["schemas"]["CostSummary"];
export type AlertRoutingPolicy = components["schemas"]["AlertRoutingPolicy"];
export type AlertDeliveryTest = components["schemas"]["AlertDeliveryTest"];
export type AlertIncident = components["schemas"]["AlertIncident"];
export type AlertSilence = components["schemas"]["AlertSilence"];
export type OpsDiagnosticRecord = components["schemas"]["OpsDiagnosticRecord"];
export type OpsExternalDependency = components["schemas"]["OpsExternalDependency"];
export type OpsDiagnosticAction = components["schemas"]["OpsDiagnosticAction"];
export type OpsDiagnosticJob = components["schemas"]["OpsDiagnosticJob"];
export type OpsDiagnosticBundle = components["schemas"]["OpsDiagnosticBundle"];
export type OpsAcceptanceSnapshot = components["schemas"]["OpsAcceptanceSnapshot"];
export type OpsAcceptanceSnapshotRequest = components["schemas"]["OpsAcceptanceSnapshotRequest"];
export type OpsPilotDailySnapshot = components["schemas"]["OpsPilotDailySnapshot"];
export type OpsPilotDailySnapshotRequest = components["schemas"]["OpsPilotDailySnapshotRequest"];
export type OpsPilotWeeklySnapshot = components["schemas"]["OpsPilotWeeklySnapshot"];
export type OpsPilotWeeklySnapshotRequest = components["schemas"]["OpsPilotWeeklySnapshotRequest"];
export type IntegrationIssue = components["schemas"]["IntegrationIssue"];
export type IntegrationIssueSummary = components["schemas"]["IntegrationIssueSummary"];
export type OpsSensitiveAccessRequest = components["schemas"]["OpsSensitiveAccessRequest"];
export type OpsSensitiveConversation = components["schemas"]["OpsSensitiveConversation"];
export type OpsSensitiveRecordingAccess = components["schemas"]["OpsSensitiveRecordingAccess"];
export type CreateSubjectRequest = components["schemas"]["CreateSubjectRequest"];
export type UpdateSubjectRequest = components["schemas"]["UpdateSubjectRequest"];
export type CreateSubjectFactRequest = components["schemas"]["CreateSubjectFactRequest"];
export type GrantConsentRequest = components["schemas"]["GrantConsentRequest"];
export type PrepareFirmwareReleaseRequest = components["schemas"]["PrepareFirmwareReleaseRequest"];
export type DeviceClaimRequest = components["schemas"]["DeviceClaimRequest"];
export type UpdateDeviceTwinRequest = components["schemas"]["UpdateDeviceTwinRequest"];
export type CreateMemberInvitationRequest = components["schemas"]["CreateMemberInvitationRequest"];
export type ConversationQuery = {
status?: components["schemas"]["ConversationStatus"];
engine_mode?: "s2s_pure" | "s2s_hybrid" | "cascade";
from?: string;
to?: string;
limit?: number;
cursor?: string;
};
type Envelope<T> = { request_id: string; data: T };
const requestTimeoutMS = 15_000;
export const AUTHENTICATION_REQUIRED_EVENT = "platform:authentication-required";
export class APIRequestError extends Error {
constructor(
message: string,
public readonly status: number,
public readonly code?: string
) {
super(message);
}
}
async function getJSON<T>(path: string, signal?: AbortSignal): Promise<T> {
const response = await fetchWithTimeout(path, {
headers: { Accept: "application/json" }
}, signal);
if (!response.ok) {
throw new Error(`请求失败(HTTP ${response.status})`);
}
return (await response.json()) as T;
}
export const platformApi = {
liveness: (signal?: AbortSignal) => getJSON<LivenessResponse>("/health/live", signal),
readiness: (signal?: AbortSignal) => getJSON<ReadinessResponse>("/health/ready", signal),
version: (signal?: AbortSignal) => getJSON<VersionResponse>("/api/v1/version", signal),
currentUser: (accessToken: string, signal?: AbortSignal) =>
getJSONWithToken<CurrentUserResponse>("/api/v1/me", accessToken, signal),
opsConfigVersions: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: OpsConfigVersion[] }>>("GET", "/api/v1/ops/config-versions", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
createOpsConfigVersion: (tenantID: string, input: components["schemas"]["CreateOpsConfigVersionRequest"], accessToken: string) =>
authenticatedJSON<Envelope<OpsConfigVersion>>("POST", "/api/v1/ops/config-versions", accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-config-${crypto.randomUUID()}` }),
submitOpsConfigVersion: (tenantID: string, item: OpsConfigVersion, accessToken: string) =>
authenticatedJSON<Envelope<OpsConfigVersion>>("PATCH", `/api/v1/ops/config-versions/${item.id}/submit`, accessToken, undefined, undefined,
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
publishOpsConfigVersion: (tenantID: string, item: OpsConfigVersion, input: OpsConfigTransitionRequest, accessToken: string) =>
authenticatedJSON<Envelope<OpsConfigVersion>>("PATCH", `/api/v1/ops/config-versions/${item.id}/publish`, accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
rollbackOpsConfigVersion: (tenantID: string, item: OpsConfigVersion, input: OpsConfigTransitionRequest, accessToken: string) =>
authenticatedJSON<Envelope<OpsConfigVersion>>("POST", `/api/v1/ops/config-versions/${item.id}/rollback`, accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-config-rollback-${crypto.randomUUID()}` }),
opsConfigEvaluations: (tenantID: string, configID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: OpsConfigEvaluation[] }>>("GET", `/api/v1/ops/config-versions/${configID}/evaluations`, accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
createOpsConfigEvaluation: (tenantID: string, configID: string, input: components["schemas"]["CreateOpsConfigEvaluationRequest"], accessToken: string) =>
authenticatedJSON<Envelope<OpsConfigEvaluation>>("POST", `/api/v1/ops/config-versions/${configID}/evaluations`, accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-config-evaluation-${crypto.randomUUID()}` }),
opsResourceReferences: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: OpsResourceReference[] }>>("GET", "/api/v1/ops/resource-references", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
createOpsResourceReference: (tenantID: string, input: components["schemas"]["CreateOpsResourceReferenceRequest"], accessToken: string) =>
authenticatedJSON<Envelope<OpsResourceReference>>("POST", "/api/v1/ops/resource-references", accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-resource-reference-${crypto.randomUUID()}` }),
revokeOpsResourceReference: (tenantID: string, item: OpsResourceReference, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsResourceReference>>("PATCH", `/api/v1/ops/resource-references/${item.id}/revoke`, accessToken, undefined, { reason },
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
opsDeviceGroups: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: OpsDeviceGroup[] }>>("GET", "/api/v1/ops/device-groups", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
createOpsDeviceGroup: (tenantID: string, input: components["schemas"]["CreateOpsDeviceGroupRequest"], accessToken: string) =>
authenticatedJSON<Envelope<OpsDeviceGroup>>("POST", "/api/v1/ops/device-groups", accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-device-group-${crypto.randomUUID()}` }),
opsConfigRollouts: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: OpsConfigRollout[] }>>("GET", "/api/v1/ops/config-rollouts", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
pauseOpsConfigRollout: (tenantID: string, item: OpsConfigRollout, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsConfigRollout>>("PATCH", `/api/v1/ops/config-rollouts/${item.id}/pause`, accessToken, undefined, { reason },
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
resumeOpsConfigRollout: (tenantID: string, item: OpsConfigRollout, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsConfigRollout>>("PATCH", `/api/v1/ops/config-rollouts/${item.id}/resume`, accessToken, undefined, { reason },
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
opsPriceBooks: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: BillingPriceBook[] }>>("GET", "/api/v1/ops/price-books", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
createOpsPriceBook: (tenantID: string, input: components["schemas"]["CreateBillingPriceBookRequest"], accessToken: string) =>
authenticatedJSON<Envelope<BillingPriceBook>>("POST", "/api/v1/ops/price-books", accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `billing-price-${crypto.randomUUID()}` }),
publishOpsPriceBook: (tenantID: string, item: BillingPriceBook, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<BillingPriceBook>>("PATCH", `/api/v1/ops/price-books/${item.id}/publish`, accessToken, undefined, { reason },
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
opsQuotaPolicies: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: QuotaPolicy[] }>>("GET", "/api/v1/ops/quota-policies", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
createOpsQuotaPolicy: (tenantID: string, input: components["schemas"]["CreateQuotaPolicyRequest"], accessToken: string) =>
authenticatedJSON<Envelope<QuotaPolicy>>("POST", "/api/v1/ops/quota-policies", accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `usage-quota-${crypto.randomUUID()}` }),
opsCostSummary: (tenantID: string, from: string, to: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<CostSummary>>("GET", `/api/v1/ops/cost-summary?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`, accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
opsAlertRoutingPolicies: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: AlertRoutingPolicy[] }>>("GET", "/api/v1/ops/alert-routing-policies", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
createOpsAlertRoutingPolicy: (tenantID: string, input: components["schemas"]["CreateAlertRoutingPolicyRequest"], accessToken: string) =>
authenticatedJSON<Envelope<AlertRoutingPolicy>>("POST", "/api/v1/ops/alert-routing-policies", accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `alert-route-${crypto.randomUUID()}` }),
opsAlertDeliveryTests: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: AlertDeliveryTest[] }>>("GET", "/api/v1/ops/alert-delivery-tests", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
createOpsAlertDeliveryTest: (tenantID: string, routingPolicyID: string, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<AlertDeliveryTest>>("POST", `/api/v1/ops/alert-routing-policies/${routingPolicyID}/delivery-tests`, accessToken, undefined, { reason },
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `alert-delivery-test-${crypto.randomUUID()}` }),
opsAlerts: (tenantID: string, status: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: AlertIncident[] }>>("GET", `/api/v1/ops/alerts${status ? `?status=${encodeURIComponent(status)}` : ""}`, accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
opsAlert: (tenantID: string, alertID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<AlertIncident>>("GET", `/api/v1/ops/alerts/${alertID}`, accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
transitionOpsAlert: (tenantID: string, item: AlertIncident, action: "acknowledge" | "mitigate" | "resolve", note: string, accessToken: string) =>
authenticatedJSON<Envelope<AlertIncident>>("POST", `/api/v1/ops/alerts/${item.id}/${action}`, accessToken, undefined, { note },
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
opsAlertSilences: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: AlertSilence[] }>>("GET", "/api/v1/ops/alert-silences", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
createOpsAlertSilence: (tenantID: string, input: components["schemas"]["CreateAlertSilenceRequest"], accessToken: string) =>
authenticatedJSON<Envelope<AlertSilence>>("POST", "/api/v1/ops/alert-silences", accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `alert-silence-${crypto.randomUUID()}` }),
revokeOpsAlertSilence: (tenantID: string, item: AlertSilence, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<AlertSilence>>("POST", `/api/v1/ops/alert-silences/${item.id}/revoke`, accessToken, undefined, { reason },
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.row_version}"` }),
opsIntegrationIssues: (tenantID: string, status: components["schemas"]["IntegrationIssueStatus"] | "", accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: IntegrationIssue[]; summary: IntegrationIssueSummary }>>("GET", `/api/v1/ops/integration-issues${status ? `?status=${encodeURIComponent(status)}` : ""}`, accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
createOpsIntegrationIssue: (tenantID: string, input: components["schemas"]["CreateIntegrationIssueRequest"], accessToken: string) =>
authenticatedJSON<Envelope<IntegrationIssue>>("POST", "/api/v1/ops/integration-issues", accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-integration-issue-${crypto.randomUUID()}` }),
updateOpsIntegrationIssue: (tenantID: string, item: IntegrationIssue, input: components["schemas"]["UpdateIntegrationIssueRequest"], accessToken: string) =>
authenticatedJSON<Envelope<IntegrationIssue>>("PATCH", `/api/v1/ops/integration-issues/${item.id}`, accessToken, undefined, input,
{ "X-Tenant-Id": tenantID, "If-Match": `"v${item.version}"` }),
searchOpsDiagnostics: (tenantID: string, field: "device_id" | "conversation_id" | "room_id" | "provider_task_ref" | "request_id", value: string, accessToken: string) =>
authenticatedJSON<Envelope<{ items: OpsDiagnosticRecord[] }>>("GET", `/api/v1/ops/diagnostics/search?field=${encodeURIComponent(field)}&value=${encodeURIComponent(value)}`, accessToken, undefined, undefined, { "X-Tenant-Id": tenantID }),
opsExternalDependencies: (tenantID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: OpsExternalDependency[] }>>("GET", "/api/v1/ops/diagnostics/external-dependencies", accessToken, signal, undefined, { "X-Tenant-Id": tenantID }),
stopOpsDiagnosticConversation: (tenantID: string, conversationID: string, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsDiagnosticAction>>("POST", `/api/v1/ops/diagnostics/conversations/${conversationID}/stop`, accessToken, undefined, { reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-stop-${crypto.randomUUID()}` }),
reconcileOpsDiagnosticConversation: (tenantID: string, conversationID: string, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsDiagnosticAction>>("POST", `/api/v1/ops/diagnostics/conversations/${conversationID}/reconcile`, accessToken, undefined, { reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-reconcile-${crypto.randomUUID()}` }),
refreshOpsDiagnosticDeviceConfig: (tenantID: string, deviceID: string, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsDiagnosticAction>>("POST", `/api/v1/ops/diagnostics/devices/${deviceID}/config-refresh`, accessToken, undefined, { reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-refresh-${crypto.randomUUID()}` }),
revokeOpsDiagnosticDevice: (tenantID: string, deviceID: string, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsDiagnosticAction>>("POST", `/api/v1/ops/diagnostics/devices/${deviceID}/revoke`, accessToken, undefined, { reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-revoke-${crypto.randomUUID()}` }),
opsDiagnosticJobs: (tenantID: string, accessToken: string) =>
authenticatedJSON<Envelope<{ items: OpsDiagnosticJob[] }>>("GET", "/api/v1/ops/diagnostics/jobs", accessToken, undefined, undefined, { "X-Tenant-Id": tenantID }),
retryOpsDiagnosticJob: (tenantID: string, jobID: string, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsDiagnosticAction>>("POST", `/api/v1/ops/diagnostics/jobs/${jobID}/retry`, accessToken, undefined, { reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-job-retry-${crypto.randomUUID()}` }),
exportOpsDiagnosticBundle: (tenantID: string, field: "device_id" | "conversation_id" | "room_id" | "provider_task_ref" | "request_id", value: string, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsDiagnosticBundle>>("POST", "/api/v1/ops/diagnostics/bundles", accessToken, undefined, { field, value, reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-diagnostic-bundle-${crypto.randomUUID()}` }),
exportOpsAcceptanceSnapshot: (tenantID: string, input: OpsAcceptanceSnapshotRequest, accessToken: string) =>
authenticatedJSON<Envelope<OpsAcceptanceSnapshot>>("POST", "/api/v1/ops/diagnostics/acceptance-snapshots", accessToken, undefined, input, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-acceptance-snapshot-${crypto.randomUUID()}` }),
exportOpsPilotDailySnapshot: (tenantID: string, input: OpsPilotDailySnapshotRequest, accessToken: string) =>
authenticatedJSON<Envelope<OpsPilotDailySnapshot>>("POST", "/api/v1/ops/diagnostics/pilot-daily-snapshots", accessToken, undefined, input, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-pilot-daily-${crypto.randomUUID()}` }),
exportOpsPilotWeeklySnapshot: (tenantID: string, input: OpsPilotWeeklySnapshotRequest, accessToken: string) =>
authenticatedJSON<Envelope<OpsPilotWeeklySnapshot>>("POST", "/api/v1/ops/diagnostics/pilot-weekly-snapshots", accessToken, undefined, input, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-pilot-weekly-${crypto.randomUUID()}` }),
opsSensitiveAccessRequests: (tenantID: string, accessToken: string) =>
authenticatedJSON<Envelope<{ items: OpsSensitiveAccessRequest[] }>>("GET", "/api/v1/ops/sensitive-access-requests", accessToken, undefined, undefined, { "X-Tenant-Id": tenantID }),
createOpsSensitiveAccessRequest: (tenantID: string, conversationID: string, capabilities: ("TRANSCRIPT_READ" | "RECORDING_PLAY")[], reason: string, expiresAt: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsSensitiveAccessRequest>>("POST", "/api/v1/ops/sensitive-access-requests", accessToken, undefined, { conversation_id: conversationID, capabilities, reason, expires_at: expiresAt }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-sensitive-access-${crypto.randomUUID()}` }),
reviewOpsSensitiveAccessRequest: (tenantID: string, requestID: string, decision: "APPROVE" | "REJECT", reason: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsSensitiveAccessRequest>>("POST", `/api/v1/ops/sensitive-access-requests/${requestID}/review`, accessToken, undefined, { decision, reason }, { "X-Tenant-Id": tenantID, "Idempotency-Key": `ops-sensitive-review-${crypto.randomUUID()}` }),
opsSensitiveConversation: (tenantID: string, conversationID: string, grantID: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsSensitiveConversation>>("GET", `/api/v1/ops/sensitive-conversations/${conversationID}`, accessToken, undefined, undefined, { "X-Tenant-Id": tenantID, "X-Ops-Access-Grant": grantID }),
createOpsSensitiveRecordingAccess: (tenantID: string, segmentID: string, grantID: string, accessToken: string) =>
authenticatedJSON<Envelope<OpsSensitiveRecordingAccess>>("POST", `/api/v1/ops/sensitive-media/${segmentID}/play`, accessToken, undefined, { grant_id: grantID }, { "X-Tenant-Id": tenantID }),
households: (accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: Household[] }>>("GET", "/api/v1/households", accessToken, signal),
createHousehold: (tenantID: string, name: string, accessToken: string) =>
authenticatedJSON<Envelope<Household>>("POST", "/api/v1/households", accessToken, undefined, { name }, { "X-Tenant-Id": tenantID }),
subjects: (householdID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: Subject[] }>>("GET", `/api/v1/households/${householdID}/subjects`, accessToken, signal),
facts: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: SubjectFact[] }>>("GET", `/api/v1/subjects/${subjectID}/facts`, accessToken, signal),
consents: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: Consent[] }>>("GET", `/api/v1/subjects/${subjectID}/consents`, accessToken, signal),
conversationReadiness: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<ConversationReadiness>>("GET", `/api/v1/subjects/${subjectID}/conversation-readiness`, accessToken, signal),
conversations: (subjectID: string, query: ConversationQuery, accessToken: string, signal?: AbortSignal) => {
const parameters = new URLSearchParams({ limit: String(query.limit ?? 20) });
if (query.status) parameters.set("status", query.status);
if (query.engine_mode) parameters.set("engine_mode", query.engine_mode);
if (query.from) parameters.set("from", query.from);
if (query.to) parameters.set("to", query.to);
if (query.cursor) parameters.set("cursor", query.cursor);
return authenticatedJSON<Envelope<{ items: ConversationSummary[]; next_cursor?: string }>>(
"GET",
`/api/v1/subjects/${subjectID}/conversations?${parameters.toString()}`,
accessToken,
signal
);
},
conversation: (conversationID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<ConversationDetail>>("GET", `/api/v1/conversations/${conversationID}`, accessToken, signal),
conversationUsage: (conversationID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<ConversationUsage>>("GET", `/api/v1/conversations/${conversationID}/usage`, accessToken, signal),
conversationTurns: (conversationID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: ConversationTurn[]; next_cursor?: string }>>("GET", `/api/v1/conversations/${conversationID}/turns?limit=100`, accessToken, signal),
conversationSummaries: (conversationID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: ConversationContentSummary[] }>>("GET", `/api/v1/conversations/${conversationID}/summaries`, accessToken, signal),
conversationMedia: (conversationID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<ConversationMedia>>("GET", `/api/v1/conversations/${conversationID}/media`, accessToken, signal),
recordingAccessGrants: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: RecordingAccessGrant[] }>>("GET", `/api/v1/subjects/${subjectID}/recording-access-grants`, accessToken, signal),
grantRecordingAccess: (subjectID: string, input: { grantee_user_id: string; permission: "PLAY" | "DOWNLOAD"; purpose: string; expires_at: string }, accessToken: string) =>
authenticatedJSON<Envelope<RecordingAccessGrant>>("POST", `/api/v1/subjects/${subjectID}/recording-access-grants`, accessToken, undefined, input),
revokeRecordingAccess: (grantID: string, accessToken: string) =>
authenticatedJSON<Envelope<RecordingAccessGrant>>("POST", `/api/v1/recording-access-grants/${grantID}/revoke`, accessToken),
createMediaAccess: (segmentID: string, action: "PLAY" | "DOWNLOAD", purpose: string, accessToken: string) =>
authenticatedJSON<Envelope<MediaAccess>>("POST", `/api/v1/media-segments/${segmentID}/access`, accessToken, undefined, { action, purpose }),
sharingGrants: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: SharingGrant[] }>>("GET", `/api/v1/sharing-grants?subject_id=${encodeURIComponent(subjectID)}`, accessToken, signal),
createSharingGrant: (input: components["schemas"]["CreateSharingGrantRequest"], accessToken: string) =>
authenticatedJSON<Envelope<SharingGrant>>("POST", "/api/v1/sharing-grants", accessToken, undefined, input,
{ "Idempotency-Key": `sharing-grant-${crypto.randomUUID()}` }),
revokeSharingGrant: (grantID: string, accessToken: string) =>
authenticatedJSON<Envelope<SharingGrant>>("DELETE", `/api/v1/sharing-grants/${grantID}`, accessToken),
stories: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: Story[] }>>("GET", `/api/v1/subjects/${subjectID}/stories`, accessToken, signal),
story: (storyID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<Story>>("GET", `/api/v1/stories/${storyID}`, accessToken, signal),
reviseStory: (story: Story, content: string, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<Story>>("PATCH", `/api/v1/stories/${story.id}`, accessToken, undefined,
{ content, reason }, { "If-Match": `"v${story.version}"` }),
confirmStory: (story: Story, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<{ story: Story; memory: CuratedMemory }>>("POST", `/api/v1/stories/${story.id}/confirm`, accessToken, undefined,
{ reason }, { "If-Match": `"v${story.version}"` }),
rejectStory: (story: Story, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<Story>>("POST", `/api/v1/stories/${story.id}/reject`, accessToken, undefined,
{ reason }, { "If-Match": `"v${story.version}"` }),
memories: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: CuratedMemory[] }>>("GET", `/api/v1/subjects/${subjectID}/memories`, accessToken, signal),
updateMemory: (memory: CuratedMemory, input: { content?: string; status?: "ACTIVE" | "DISABLED"; reason: string }, accessToken: string) =>
authenticatedJSON<Envelope<CuratedMemory>>("PATCH", `/api/v1/memories/${memory.id}`, accessToken, undefined,
input, { "If-Match": `"v${memory.version}"` }),
deleteMemory: (memory: CuratedMemory, reason: string, accessToken: string) =>
authenticatedJSON<Envelope<CuratedMemory>>("DELETE", `/api/v1/memories/${memory.id}`, accessToken, undefined,
{ reason }, { "If-Match": `"v${memory.version}"` }),
privacyRequests: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: PrivacyRequest[] }>>("GET", `/api/v1/subjects/${subjectID}/privacy-requests`, accessToken, signal),
privacyRequest: (requestID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<PrivacyRequest>>("GET", `/api/v1/privacy-requests/${requestID}`, accessToken, signal),
createPrivacyAccessRequest: (subjectID: string, categories: string[], accessToken: string) =>
authenticatedJSON<Envelope<PrivacyRequest>>("POST", `/api/v1/subjects/${subjectID}/privacy-requests`, accessToken, undefined,
{ request_type: "ACCESS", scope: { categories } }, { "Idempotency-Key": `privacy-access-${crypto.randomUUID()}` }),
createPrivacyDeleteRequest: (subjectID: string, reason: string, confirmation: string, accessToken: string) =>
authenticatedJSON<Envelope<PrivacyRequest>>("POST", `/api/v1/subjects/${subjectID}/privacy-requests`, accessToken, undefined,
{ request_type: "DELETE", scope: {}, reason, confirmation }, { "Idempotency-Key": `privacy-delete-${crypto.randomUUID()}` }),
exportTasks: (subjectID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: ExportTask[] }>>("GET", `/api/v1/subjects/${subjectID}/exports`, accessToken, signal),
exportTask: (exportID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<ExportTask>>("GET", `/api/v1/exports/${exportID}`, accessToken, signal),
createExportTask: (subjectID: string, categories: string[], accessToken: string) =>
authenticatedJSON<Envelope<ExportTask>>("POST", `/api/v1/subjects/${subjectID}/exports`, accessToken, undefined,
{ scope: { categories } }, { "Idempotency-Key": `subject-export-${crypto.randomUUID()}` }),
devices: (accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: ManagedDevice[] }>>("GET", "/api/v1/devices", accessToken, signal),
devicePresence: (deviceID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<DevicePresence>>("GET", `/api/v1/devices/${deviceID}/presence`, accessToken, signal),
deviceTwin: (deviceID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<DeviceTwin>>("GET", `/api/v1/devices/${deviceID}/twin`, accessToken, signal),
members: (householdID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: HouseholdMember[] }>>("GET", `/api/v1/households/${householdID}/members`, accessToken, signal),
invitations: (householdID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: MemberInvitation[] }>>("GET", `/api/v1/households/${householdID}/invitations`, accessToken, signal),
createInvitation: (householdID: string, input: CreateMemberInvitationRequest, accessToken: string) =>
authenticatedJSON<Envelope<MemberInvitation>>("POST", `/api/v1/households/${householdID}/invitations`, accessToken, undefined, input),
revokeInvitation: (householdID: string, invitationID: string, accessToken: string) =>
authenticatedJSON<Envelope<MemberInvitation>>("POST", `/api/v1/households/${householdID}/invitations/${invitationID}/revoke`, accessToken),
previewInvitation: (token: string, accessToken: string) =>
authenticatedJSON<Envelope<MemberInvitationPreview>>("POST", "/api/v1/member-invitations/preview", accessToken, undefined, { token }),
acceptInvitation: (token: string, accessToken: string) =>
authenticatedJSON<Envelope<HouseholdMember>>("POST", "/api/v1/member-invitations/accept", accessToken, undefined, { token }),
updateMemberRole: (householdID: string, member: HouseholdMember, role: "admin" | "editor" | "viewer", accessToken: string) =>
authenticatedJSON<Envelope<HouseholdMember>>("PATCH", `/api/v1/households/${householdID}/members/${member.id}`, accessToken, undefined,
{ role }, { "If-Match": `"v${member.version}"` }),
removeMember: (householdID: string, member: HouseholdMember, accessToken: string) =>
authenticatedJSON<Envelope<HouseholdMember>>("DELETE", `/api/v1/households/${householdID}/members/${member.id}`, accessToken, undefined,
undefined, { "If-Match": `"v${member.version}"` }),
transferOwnership: (household: Household, memberID: string, accessToken: string) =>
authenticatedJSON<Envelope<Household>>("POST", `/api/v1/households/${household.id}/ownership`, accessToken, undefined,
{ member_id: memberID }, { "If-Match": `"v${household.version}"` }),
createSubject: (householdID: string, input: CreateSubjectRequest, accessToken: string) =>
authenticatedJSON<Envelope<Subject>>("POST", `/api/v1/households/${householdID}/subjects`, accessToken, undefined, input),
updateSubject: (subject: Subject, input: UpdateSubjectRequest, accessToken: string) =>
authenticatedJSON<Envelope<Subject>>("PATCH", `/api/v1/subjects/${subject.id}`, accessToken, undefined, input,
{ "If-Match": `"v${subject.version}"` }),
createFact: (subjectID: string, input: CreateSubjectFactRequest, accessToken: string) =>
authenticatedJSON<Envelope<SubjectFact>>("POST", `/api/v1/subjects/${subjectID}/facts`, accessToken, undefined, input),
confirmFact: (subjectID: string, fact: SubjectFact, accessToken: string) =>
authenticatedJSON<Envelope<SubjectFact>>("PATCH", `/api/v1/subjects/${subjectID}/facts/${fact.id}`, accessToken, undefined,
{ confirmation_status: "confirmed" }, { "If-Match": `"v${fact.version}"` }),
grantConsent: (subjectID: string, input: GrantConsentRequest, accessToken: string) =>
authenticatedJSON<Envelope<Consent>>("POST", `/api/v1/subjects/${subjectID}/consents`, accessToken, undefined, input),
withdrawConsent: (subjectID: string, consentID: string, accessToken: string) =>
authenticatedJSON<Envelope<Consent>>("POST", `/api/v1/subjects/${subjectID}/consents/${consentID}/withdraw`, accessToken),
claimDevice: (input: DeviceClaimRequest, accessToken: string) =>
authenticatedJSON<Envelope<components["schemas"]["DeviceClaimResult"]>>("POST", "/api/v1/device-claims", accessToken, undefined, input),
updateDeviceTwin: (deviceID: string, revision: number, input: UpdateDeviceTwinRequest, accessToken: string) =>
authenticatedJSON<Envelope<DeviceTwin>>("PATCH", `/api/v1/devices/${deviceID}/twin`, accessToken, undefined, input,
{ "If-Match": `"v${revision}"` })
,createDeviceCommand: (deviceID: string, type: "apply_config" | "reboot" | "diagnostics" | "firmware_update", payload: Record<string, unknown>, accessToken: string) =>
authenticatedJSON<Envelope<DeviceCommand>>("POST", `/api/v1/devices/${deviceID}/commands`, accessToken, undefined,
{ type, payload, expires_in_sec: 300 }, { "Idempotency-Key": `device-command-${crypto.randomUUID()}` })
,firmwareReleases: (householdID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: FirmwareRelease[] }>>("GET", `/api/v1/households/${householdID}/firmware-releases`, accessToken, signal)
,firmwareReleaseImpact: (releaseID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<FirmwareReleaseImpact>>("GET", `/api/v1/firmware-releases/${releaseID}/impact`, accessToken, signal)
,registerFirmwareSigningKey: (householdID: string, input: { key_id: string; public_key_base64: string }, accessToken: string) =>
authenticatedJSON<Envelope<{ key_id: string; status: string }>>("POST", `/api/v1/households/${householdID}/firmware-signing-keys`, accessToken, undefined, input)
,firmwareSigningKeys: (householdID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: FirmwareSigningKey[] }>>("GET", `/api/v1/households/${householdID}/firmware-signing-keys`, accessToken, signal)
,revokeFirmwareSigningKey: (householdID: string, keyID: string, accessToken: string) =>
authenticatedJSON<Envelope<FirmwareSigningKey>>("POST", `/api/v1/households/${householdID}/firmware-signing-keys/revoke`, accessToken, undefined, { key_id: keyID })
,prepareFirmwareRelease: (householdID: string, input: components["schemas"]["PrepareFirmwareReleaseRequest"], accessToken: string) =>
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/households/${householdID}/firmware-releases`, accessToken, undefined, input)
,finalizeFirmwareRelease: (releaseID: string, accessToken: string) =>
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/firmware-releases/${releaseID}/finalize`, accessToken)
,renewFirmwareUpload: (releaseID: string, accessToken: string) =>
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/firmware-releases/${releaseID}/upload-session`, accessToken)
,pauseFirmwareRelease: (releaseID: string, accessToken: string) =>
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/firmware-releases/${releaseID}/pause`, accessToken)
,resumeFirmwareRelease: (releaseID: string, accessToken: string) =>
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/firmware-releases/${releaseID}/resume`, accessToken)
,retireFirmwareRelease: (releaseID: string, accessToken: string) =>
authenticatedJSON<Envelope<FirmwareRelease>>("POST", `/api/v1/firmware-releases/${releaseID}/retire`, accessToken)
,firmwareRollouts: (householdID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<{ items: FirmwareRollout[] }>>("GET", `/api/v1/households/${householdID}/firmware-rollouts`, accessToken, signal)
,firmwareRollout: (rolloutID: string, accessToken: string, signal?: AbortSignal) =>
authenticatedJSON<Envelope<FirmwareRollout>>("GET", `/api/v1/firmware-rollouts/${rolloutID}`, accessToken, signal)
,startFirmwareRollout: (householdID: string, input: components["schemas"]["StartFirmwareRolloutRequest"], accessToken: string) =>
authenticatedJSON<Envelope<FirmwareRollout>>("POST", `/api/v1/households/${householdID}/firmware-rollouts`, accessToken, undefined, input)
,pauseFirmwareRollout: (rolloutID: string, accessToken: string) =>
authenticatedJSON<Envelope<FirmwareRollout>>("POST", `/api/v1/firmware-rollouts/${rolloutID}/pause`, accessToken)
,rollbackFirmwareRollout: (rolloutID: string, rollbackReleaseID: string, accessToken: string) =>
authenticatedJSON<Envelope<FirmwareRollout>>("POST", `/api/v1/firmware-rollouts/${rolloutID}/rollback`, accessToken, undefined,
{ rollback_release_id: rollbackReleaseID })
};
async function getJSONWithToken<T>(path: string, accessToken: string, signal?: AbortSignal): Promise<T> {
return authenticatedJSON<T>("GET", path, accessToken, signal);
}
async function authenticatedJSON<T>(
method: string,
path: string,
accessToken: string,
signal?: AbortSignal,
body?: unknown,
extraHeaders: Record<string, string> = {}
): Promise<T> {
const response = await fetchWithTimeout(path, {
method,
body: body === undefined ? undefined : JSON.stringify(body),
headers: {
Accept: "application/json",
Authorization: `Bearer ${accessToken}`,
...(body === undefined ? {} : { "Content-Type": "application/json; charset=utf-8" }),
...extraHeaders
}
}, signal);
if (!response.ok) {
let code: string | undefined;
let message = response.status === 401 ? "登录状态已经失效,请重新登录" : `请求失败(HTTP ${response.status})`;
try {
const body = (await response.json()) as { error?: { code?: string; message?: string } };
code = body.error?.code;
if (body.error?.message) message = body.error.message;
} catch {
// Keep the stable HTTP fallback when a proxy returned a non-JSON response.
}
if (response.status === 401) window.dispatchEvent(new Event(AUTHENTICATION_REQUIRED_EVENT));
throw new APIRequestError(message, response.status, code);
}
return (await response.json()) as T;
}
async function fetchWithTimeout(input: RequestInfo | URL, init: RequestInit, callerSignal?: AbortSignal): Promise<Response> {
const controller = new AbortController();
const onCallerAbort = () => controller.abort(callerSignal?.reason);
if (callerSignal?.aborted) onCallerAbort();
else callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
const timeout = window.setTimeout(() => controller.abort(new DOMException("请求超时", "TimeoutError")), requestTimeoutMS);
try {
return await fetch(input, { ...init, signal: controller.signal });
} catch (error) {
if (!callerSignal?.aborted && controller.signal.aborted) {
throw new APIRequestError("请求超时,请检查网络后重试", 0, "REQUEST_TIMEOUT");
}
throw error;
} finally {
window.clearTimeout(timeout);
callerSignal?.removeEventListener("abort", onCallerAbort);
}
}
+11007
View File
File diff suppressed because it is too large Load Diff