591 lines
21 KiB
TypeScript
591 lines
21 KiB
TypeScript
import { hostApiFetch } from '@/lib/host-api';
|
|
|
|
const API_ROOT = '/api/works/ai-hardware';
|
|
const DEFAULT_ERROR_CODE = 'AI_HARDWARE_REQUEST_FAILED';
|
|
const SAFE_ERROR_MESSAGES: Record<string, string> = {
|
|
ai_hardware_resource_not_found: 'AI hardware resource was not found',
|
|
ai_hardware_idempotency_conflict: 'This operation conflicts with an earlier request',
|
|
ai_hardware_operation_in_progress: 'AI hardware operation is still in progress',
|
|
ai_hardware_device_already_bound: 'AI hardware device is already bound',
|
|
ai_hardware_revision_conflict: 'AI hardware state changed; refresh and retry',
|
|
ai_hardware_state_conflict: 'AI hardware operation conflicts with the current state',
|
|
ai_hardware_activation_code_invalid: 'AI hardware activation code is invalid',
|
|
ai_hardware_request_rejected: 'AI hardware request was rejected',
|
|
ai_hardware_revision_required: 'A current AI hardware revision is required',
|
|
ai_hardware_provider_state_conflict: 'AI hardware provider state is inconsistent',
|
|
xiaozhi_hardware_protocol_error: 'AI hardware service returned an invalid response',
|
|
ai_hardware_unconfigured: 'AI hardware integration is not configured',
|
|
ai_hardware_credential_recovery_required: 'AI hardware credential recovery is required',
|
|
ai_hardware_credential_unavailable: 'AI hardware credential is unavailable',
|
|
ai_hardware_credential_recovery_unavailable: 'AI hardware credential recovery is not currently available',
|
|
ai_hardware_catalog_unavailable: 'AI hardware configuration options are unavailable',
|
|
xiaozhi_hardware_unavailable: 'AI hardware service is unavailable',
|
|
xiaozhi_hardware_timeout: 'AI hardware service timed out',
|
|
ai_hardware_disabled: 'AI hardware module is not enabled',
|
|
AI_HARDWARE_DISABLED: 'AI hardware module is not enabled',
|
|
AI_HARDWARE_TIMEOUT: 'AI hardware service timed out',
|
|
AI_HARDWARE_UNAVAILABLE: 'AI hardware service is unavailable',
|
|
AI_HARDWARE_TRANSPORT_ERROR: 'AI hardware request could not reach the local service',
|
|
AI_HARDWARE_REVISION_CONFLICT: 'AI hardware state changed; refresh and retry',
|
|
AI_HARDWARE_AUTH_REQUIRED: 'Works Square sign-in is required',
|
|
AI_HARDWARE_FORBIDDEN: 'AI hardware operation is not allowed',
|
|
AI_HARDWARE_RESOURCE_NOT_FOUND: 'AI hardware resource was not found',
|
|
AI_HARDWARE_RATE_LIMITED: 'AI hardware service is busy; retry later',
|
|
AI_HARDWARE_INVALID_REQUEST: 'Invalid AI hardware request',
|
|
AI_HARDWARE_INVALID_RESPONSE: 'AI hardware service returned an invalid response',
|
|
};
|
|
const OPERATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
|
|
export type AiHardwareStatus =
|
|
| 'unprovisioned'
|
|
| 'provisioning'
|
|
| 'active'
|
|
| 'credential_recovery_required'
|
|
| 'invalid';
|
|
|
|
export type AiHardwareAgent = {
|
|
id: string;
|
|
name: string;
|
|
config_revision: number;
|
|
};
|
|
|
|
export type AiHardwareDevice = {
|
|
id: string;
|
|
agent_id: string;
|
|
assignment_revision: number;
|
|
};
|
|
|
|
export type AiHardwareOverview = {
|
|
status: AiHardwareStatus;
|
|
agents: AiHardwareAgent[];
|
|
devices: AiHardwareDevice[];
|
|
};
|
|
|
|
export type AiHardwareAgentConfiguration = AiHardwareAgent & {
|
|
system_prompt: string | null;
|
|
lang_code: string | null;
|
|
language: string | null;
|
|
asr_model_id: string | null;
|
|
vad_model_id: string | null;
|
|
llm_model_id: string | null;
|
|
slm_model_id: string | null;
|
|
vllm_model_id: string | null;
|
|
tts_model_id: string | null;
|
|
tts_voice_id: string | null;
|
|
tts_language: string | null;
|
|
tts_volume: number | null;
|
|
tts_rate: number | null;
|
|
tts_pitch: number | null;
|
|
mem_model_id: string | null;
|
|
intent_model_id: string | null;
|
|
chat_history_conf: number | null;
|
|
};
|
|
|
|
export type AiHardwareCatalogModelType =
|
|
| 'VAD'
|
|
| 'ASR'
|
|
| 'LLM'
|
|
| 'VLLM'
|
|
| 'Intent'
|
|
| 'Memory'
|
|
| 'TTS';
|
|
|
|
export type AiHardwareCatalogModel = {
|
|
model_type: AiHardwareCatalogModelType;
|
|
model_id: string;
|
|
model_name: string;
|
|
supports_function_call: boolean | null;
|
|
};
|
|
|
|
export type AiHardwareCatalogVoice = {
|
|
tts_model_id: string;
|
|
voice_id: string;
|
|
voice_name: string;
|
|
languages: string[];
|
|
is_clone: boolean;
|
|
};
|
|
|
|
export type AiHardwareConfigurationCatalog = {
|
|
schema_version: 1;
|
|
models: AiHardwareCatalogModel[];
|
|
voices: AiHardwareCatalogVoice[];
|
|
};
|
|
|
|
export const AI_HARDWARE_CLEARABLE_FIELDS = [
|
|
'system_prompt',
|
|
'lang_code',
|
|
'language',
|
|
'asr_model_id',
|
|
'vad_model_id',
|
|
'llm_model_id',
|
|
'slm_model_id',
|
|
'vllm_model_id',
|
|
'tts_model_id',
|
|
'tts_voice_id',
|
|
'tts_language',
|
|
'tts_volume',
|
|
'tts_rate',
|
|
'tts_pitch',
|
|
'mem_model_id',
|
|
'intent_model_id',
|
|
] as const;
|
|
|
|
export type AiHardwareClearableField = typeof AI_HARDWARE_CLEARABLE_FIELDS[number];
|
|
|
|
export type AiHardwareAgentConfigurationUpdate = Partial<{
|
|
agent_name: string;
|
|
system_prompt: string;
|
|
lang_code: string;
|
|
language: string;
|
|
asr_model_id: string;
|
|
vad_model_id: string;
|
|
llm_model_id: string;
|
|
slm_model_id: string;
|
|
vllm_model_id: string;
|
|
tts_model_id: string;
|
|
tts_voice_id: string;
|
|
tts_language: string;
|
|
tts_volume: number;
|
|
tts_rate: number;
|
|
tts_pitch: number;
|
|
mem_model_id: string;
|
|
intent_model_id: string;
|
|
chat_history_conf: number;
|
|
}> & { clear_fields?: AiHardwareClearableField[] };
|
|
|
|
export type VersionedAiHardwareResult<T> = {
|
|
data: T;
|
|
revision: number;
|
|
};
|
|
|
|
type MainEnvelope = {
|
|
success?: unknown;
|
|
status?: unknown;
|
|
code?: unknown;
|
|
error?: unknown;
|
|
retryable?: unknown;
|
|
retry_after_seconds?: unknown;
|
|
operation_id?: unknown;
|
|
data?: unknown;
|
|
revision?: unknown;
|
|
};
|
|
|
|
export class AiHardwareApiError extends Error {
|
|
readonly status: number;
|
|
readonly code: string;
|
|
readonly retryable: boolean;
|
|
readonly retryAfterSeconds: number | null;
|
|
readonly operationId: string | null;
|
|
|
|
constructor(options: {
|
|
status: number;
|
|
code: string;
|
|
message: string;
|
|
retryable?: boolean;
|
|
retryAfterSeconds?: number | null;
|
|
operationId?: string | null;
|
|
}) {
|
|
super(options.message);
|
|
this.name = 'AiHardwareApiError';
|
|
this.status = options.status;
|
|
this.code = options.code;
|
|
this.retryable = options.retryable ?? false;
|
|
this.retryAfterSeconds = options.retryAfterSeconds ?? null;
|
|
this.operationId = options.operationId ?? null;
|
|
}
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
function isInteger(value: unknown, min = 0, max = Number.MAX_SAFE_INTEGER): value is number {
|
|
return Number.isSafeInteger(value) && (value as number) >= min && (value as number) <= max;
|
|
}
|
|
|
|
function hasOnlyKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
const allowed = new Set(keys);
|
|
return Object.keys(value).every((key) => allowed.has(key));
|
|
}
|
|
|
|
function invalidPayload(): never {
|
|
throw new AiHardwareApiError({
|
|
status: 502,
|
|
code: 'AI_HARDWARE_INVALID_RESPONSE',
|
|
message: 'AI hardware service returned an invalid response',
|
|
});
|
|
}
|
|
|
|
function readAgent(value: unknown): AiHardwareAgent {
|
|
if (!isRecord(value) || !hasOnlyKeys(value, ['id', 'name', 'config_revision'])
|
|
|| typeof value.id !== 'string' || value.id.length < 1 || value.id.length > 36
|
|
|| typeof value.name !== 'string' || value.name.length < 1 || value.name.length > 64
|
|
|| !isInteger(value.config_revision)) invalidPayload();
|
|
return value as AiHardwareAgent;
|
|
}
|
|
|
|
function readDevice(value: unknown): AiHardwareDevice {
|
|
if (!isRecord(value) || !hasOnlyKeys(value, ['id', 'agent_id', 'assignment_revision'])
|
|
|| typeof value.id !== 'string' || value.id.length < 1 || value.id.length > 36
|
|
|| typeof value.agent_id !== 'string' || value.agent_id.length < 1 || value.agent_id.length > 36
|
|
|| !isInteger(value.assignment_revision)) invalidPayload();
|
|
return value as AiHardwareDevice;
|
|
}
|
|
|
|
function nullableString(value: unknown): boolean {
|
|
return value === null || typeof value === 'string';
|
|
}
|
|
|
|
function nullableBoundedInteger(value: unknown, min: number, max: number): boolean {
|
|
return value === null || isInteger(value, min, max);
|
|
}
|
|
|
|
const CONFIG_KEYS = [
|
|
'id', 'name', 'config_revision', 'system_prompt', 'lang_code', 'language',
|
|
'asr_model_id', 'vad_model_id', 'llm_model_id', 'slm_model_id', 'vllm_model_id',
|
|
'tts_model_id', 'tts_voice_id', 'tts_language', 'tts_volume', 'tts_rate',
|
|
'tts_pitch', 'mem_model_id', 'intent_model_id', 'chat_history_conf',
|
|
] as const;
|
|
|
|
function readConfiguration(value: unknown): AiHardwareAgentConfiguration {
|
|
if (!isRecord(value) || !hasOnlyKeys(value, CONFIG_KEYS)
|
|
|| Object.keys(value).length !== CONFIG_KEYS.length) invalidPayload();
|
|
readAgent({ id: value.id, name: value.name, config_revision: value.config_revision });
|
|
for (const key of [
|
|
'system_prompt', 'lang_code', 'language', 'asr_model_id', 'vad_model_id',
|
|
'llm_model_id', 'slm_model_id', 'vllm_model_id', 'tts_model_id', 'tts_voice_id',
|
|
'tts_language', 'mem_model_id', 'intent_model_id',
|
|
] as const) {
|
|
if (!nullableString(value[key])) invalidPayload();
|
|
}
|
|
for (const key of ['tts_volume', 'tts_rate', 'tts_pitch'] as const) {
|
|
if (!nullableBoundedInteger(value[key], -100, 100)) invalidPayload();
|
|
}
|
|
if (!nullableBoundedInteger(value.chat_history_conf, 0, 2)) invalidPayload();
|
|
return value as AiHardwareAgentConfiguration;
|
|
}
|
|
|
|
function readOverview(value: unknown): AiHardwareOverview {
|
|
const statuses: AiHardwareStatus[] = [
|
|
'unprovisioned', 'provisioning', 'active', 'credential_recovery_required', 'invalid',
|
|
];
|
|
if (!isRecord(value) || !hasOnlyKeys(value, ['status', 'agents', 'devices'])
|
|
|| typeof value.status !== 'string'
|
|
|| !statuses.includes(value.status as AiHardwareStatus)
|
|
|| !Array.isArray(value.agents) || !Array.isArray(value.devices)) invalidPayload();
|
|
return {
|
|
status: value.status as AiHardwareStatus,
|
|
agents: value.agents.map(readAgent),
|
|
devices: value.devices.map(readDevice),
|
|
};
|
|
}
|
|
|
|
const CATALOG_MODEL_TYPES = new Set<AiHardwareCatalogModelType>([
|
|
'VAD', 'ASR', 'LLM', 'VLLM', 'Intent', 'Memory', 'TTS',
|
|
]);
|
|
|
|
function boundedString(value: unknown, maxLength: number): value is string {
|
|
return typeof value === 'string' && value.length > 0 && value.length <= maxLength;
|
|
}
|
|
|
|
function readCatalogModel(value: unknown): AiHardwareCatalogModel {
|
|
if (!isRecord(value)
|
|
|| !hasOnlyKeys(value, ['model_type', 'model_id', 'model_name', 'supports_function_call'])
|
|
|| Object.keys(value).length !== 4
|
|
|| typeof value.model_type !== 'string'
|
|
|| !CATALOG_MODEL_TYPES.has(value.model_type as AiHardwareCatalogModelType)
|
|
|| !boundedString(value.model_id, 255)
|
|
|| !boundedString(value.model_name, 128)
|
|
|| (value.supports_function_call !== null && typeof value.supports_function_call !== 'boolean')) {
|
|
invalidPayload();
|
|
}
|
|
return value as AiHardwareCatalogModel;
|
|
}
|
|
|
|
function readCatalogVoice(value: unknown): AiHardwareCatalogVoice {
|
|
if (!isRecord(value)
|
|
|| !hasOnlyKeys(value, ['tts_model_id', 'voice_id', 'voice_name', 'languages', 'is_clone'])
|
|
|| Object.keys(value).length !== 5
|
|
|| !boundedString(value.tts_model_id, 255)
|
|
|| !boundedString(value.voice_id, 255)
|
|
|| !boundedString(value.voice_name, 128)
|
|
|| !Array.isArray(value.languages)
|
|
|| value.languages.length > 16
|
|
|| value.languages.some((language) => !boundedString(language, 50))
|
|
|| typeof value.is_clone !== 'boolean') {
|
|
invalidPayload();
|
|
}
|
|
return value as AiHardwareCatalogVoice;
|
|
}
|
|
|
|
function readConfigurationCatalog(value: unknown): AiHardwareConfigurationCatalog {
|
|
if (!isRecord(value)
|
|
|| !hasOnlyKeys(value, ['schema_version', 'models', 'voices'])
|
|
|| Object.keys(value).length !== 3
|
|
|| value.schema_version !== 1
|
|
|| !Array.isArray(value.models)
|
|
|| value.models.length > 256
|
|
|| !Array.isArray(value.voices)
|
|
|| value.voices.length > 512) {
|
|
invalidPayload();
|
|
}
|
|
return {
|
|
schema_version: 1,
|
|
models: value.models.map(readCatalogModel),
|
|
voices: value.voices.map(readCatalogVoice),
|
|
};
|
|
}
|
|
|
|
function readEnvelope(value: unknown): MainEnvelope {
|
|
if (!isRecord(value) || !hasOnlyKeys(value, [
|
|
'success', 'status', 'code', 'error', 'retryable', 'retry_after_seconds', 'operation_id', 'data', 'revision',
|
|
])) invalidPayload();
|
|
return value;
|
|
}
|
|
|
|
function throwEnvelopeError(envelope: MainEnvelope): never {
|
|
const status = isInteger(envelope.status, 400, 599) ? envelope.status : 502;
|
|
const code = typeof envelope.code === 'string' && /^[A-Za-z0-9._-]{1,128}$/.test(envelope.code)
|
|
? envelope.code
|
|
: DEFAULT_ERROR_CODE;
|
|
const retryAfterSeconds = isInteger(envelope.retry_after_seconds, 0, 86_400)
|
|
? envelope.retry_after_seconds
|
|
: null;
|
|
const operationId = typeof envelope.operation_id === 'string' && OPERATION_ID.test(envelope.operation_id)
|
|
? envelope.operation_id.toLowerCase()
|
|
: null;
|
|
throw new AiHardwareApiError({
|
|
status,
|
|
code,
|
|
message: SAFE_ERROR_MESSAGES[code] ?? `AI hardware request failed (${status})`,
|
|
retryable: envelope.retryable === true,
|
|
retryAfterSeconds,
|
|
operationId,
|
|
});
|
|
}
|
|
|
|
async function request<T>(
|
|
path: string,
|
|
init: RequestInit | undefined,
|
|
read: (value: unknown) => T,
|
|
versioned = false,
|
|
): Promise<T | VersionedAiHardwareResult<T>> {
|
|
const envelope = readEnvelope(await hostApiFetch<unknown>(`${API_ROOT}${path}`, init));
|
|
if (envelope.success !== true) throwEnvelopeError(envelope);
|
|
const data = read(envelope.data);
|
|
if (!versioned) return data;
|
|
if (!isInteger(envelope.revision)) invalidPayload();
|
|
return { data, revision: envelope.revision };
|
|
}
|
|
|
|
function jsonInit(method: string, body: unknown): RequestInit {
|
|
return { method, body: JSON.stringify(body) };
|
|
}
|
|
|
|
function assertRevision(revision: number): void {
|
|
if (!isInteger(revision)) throw new TypeError('revision must be a non-negative integer');
|
|
}
|
|
|
|
function assertNoNull(value: Record<string, unknown>): void {
|
|
if (Object.values(value).some((item) => item === null)) {
|
|
throw new TypeError('AI hardware updates must use clear_fields instead of null');
|
|
}
|
|
}
|
|
|
|
function assertString(value: unknown, field: string, maxLength: number): asserts value is string {
|
|
if (typeof value !== 'string' || value.length < 1 || value.length > maxLength) {
|
|
throw new TypeError(`${field} is invalid`);
|
|
}
|
|
}
|
|
|
|
function assertAgentId(value: string): void {
|
|
assertString(value, 'agent_id', 36);
|
|
}
|
|
|
|
async function mutationRequest<T>(
|
|
path: string,
|
|
method: string,
|
|
body: Record<string, unknown>,
|
|
operationIdValue: string,
|
|
read: (value: unknown) => T,
|
|
versioned = false,
|
|
): Promise<T | VersionedAiHardwareResult<T>> {
|
|
try {
|
|
return await request(path, jsonInit(method, {
|
|
...body,
|
|
client_operation_id: operationIdValue,
|
|
}), read, versioned);
|
|
} catch (error) {
|
|
if (isRecord(error) && error.name === 'AiHardwareApiError') {
|
|
if (error.operationId) throw error;
|
|
const structured = error as unknown as AiHardwareApiError;
|
|
throw new AiHardwareApiError({
|
|
status: structured.status,
|
|
code: structured.code,
|
|
message: structured.message,
|
|
retryable: structured.retryable,
|
|
retryAfterSeconds: structured.retryAfterSeconds,
|
|
operationId: operationIdValue,
|
|
});
|
|
}
|
|
throw new AiHardwareApiError({
|
|
status: 502,
|
|
code: 'AI_HARDWARE_TRANSPORT_ERROR',
|
|
message: 'AI hardware request could not reach the local service',
|
|
retryable: true,
|
|
operationId: operationIdValue,
|
|
});
|
|
}
|
|
}
|
|
|
|
export type AiHardwareMutationOptions = { operationId?: string };
|
|
|
|
export function createAiHardwareOperationId(): string {
|
|
return globalThis.crypto.randomUUID();
|
|
}
|
|
|
|
function operationId(options?: AiHardwareMutationOptions): string {
|
|
const value = options?.operationId ?? createAiHardwareOperationId();
|
|
if (!OPERATION_ID.test(value)) throw new TypeError('operationId is invalid');
|
|
return value.toLowerCase();
|
|
}
|
|
|
|
const UPDATE_STRING_LIMITS: Record<string, number> = {
|
|
agent_name: 64,
|
|
system_prompt: 16_000,
|
|
lang_code: 10,
|
|
language: 10,
|
|
asr_model_id: 32,
|
|
vad_model_id: 64,
|
|
llm_model_id: 32,
|
|
slm_model_id: 255,
|
|
vllm_model_id: 32,
|
|
tts_model_id: 32,
|
|
tts_voice_id: 32,
|
|
tts_language: 50,
|
|
mem_model_id: 32,
|
|
intent_model_id: 32,
|
|
};
|
|
|
|
function validateUpdate(update: AiHardwareAgentConfigurationUpdate): void {
|
|
const record = update as Record<string, unknown>;
|
|
assertNoNull(record);
|
|
const allowed = new Set([...CONFIG_KEYS.slice(1), 'agent_name', 'clear_fields']);
|
|
allowed.delete('name'); allowed.delete('config_revision');
|
|
if (!hasOnlyKeys(record, [...allowed])) throw new TypeError('Unsupported AI hardware field');
|
|
const clears = update.clear_fields ?? [];
|
|
const clearable = new Set<string>(AI_HARDWARE_CLEARABLE_FIELDS);
|
|
if (clears.length > 16 || new Set(clears).size !== clears.length
|
|
|| clears.some((field) => !clearable.has(field))) {
|
|
throw new TypeError('Invalid clear_fields');
|
|
}
|
|
if (Object.keys(record).filter((key) => key !== 'clear_fields').some((key) => clears.includes(key as AiHardwareClearableField))) {
|
|
throw new TypeError('A field cannot be set and cleared together');
|
|
}
|
|
if (Object.keys(record).length === 0
|
|
|| (Object.keys(record).length === 1 && 'clear_fields' in record && clears.length === 0)) {
|
|
throw new TypeError('At least one configuration change is required');
|
|
}
|
|
for (const [field, maxLength] of Object.entries(UPDATE_STRING_LIMITS)) {
|
|
if (field in record) assertString(record[field], field, maxLength);
|
|
}
|
|
for (const field of ['tts_volume', 'tts_rate', 'tts_pitch']) {
|
|
if (field in record && !isInteger(record[field], -100, 100)) {
|
|
throw new TypeError(`${field} is invalid`);
|
|
}
|
|
}
|
|
if ('chat_history_conf' in record && !isInteger(record.chat_history_conf, 0, 2)) {
|
|
throw new TypeError('chat_history_conf is invalid');
|
|
}
|
|
}
|
|
|
|
export async function getAiHardwareOverview(): Promise<AiHardwareOverview> {
|
|
return request('', undefined, readOverview) as Promise<AiHardwareOverview>;
|
|
}
|
|
|
|
export async function getAiHardwareConfigurationCatalog(
|
|
ttsModelId?: string,
|
|
): Promise<AiHardwareConfigurationCatalog> {
|
|
if (ttsModelId !== undefined) assertString(ttsModelId, 'tts_model_id', 255);
|
|
const query = ttsModelId === undefined
|
|
? ''
|
|
: `?tts_model_id=${encodeURIComponent(ttsModelId)}`;
|
|
return request(`/catalog${query}`, undefined, readConfigurationCatalog) as Promise<AiHardwareConfigurationCatalog>;
|
|
}
|
|
|
|
export async function recoverAiHardwareCredential(
|
|
options?: AiHardwareMutationOptions,
|
|
): Promise<AiHardwareOverview> {
|
|
const operationIdValue = operationId(options);
|
|
return mutationRequest(
|
|
'/credential-recovery', 'POST', {}, operationIdValue, readOverview,
|
|
) as Promise<AiHardwareOverview>;
|
|
}
|
|
|
|
export async function createAiHardwareAgent(agentName: string, options?: AiHardwareMutationOptions): Promise<AiHardwareAgent> {
|
|
const normalizedName = agentName.trim();
|
|
assertString(normalizedName, 'agent_name', 64);
|
|
const operationIdValue = operationId(options);
|
|
return mutationRequest('/agents', 'POST', { agent_name: normalizedName }, operationIdValue, readAgent) as Promise<AiHardwareAgent>;
|
|
}
|
|
|
|
export async function bindAiHardwareDevice(
|
|
activationCode: string,
|
|
agentId: string,
|
|
options?: AiHardwareMutationOptions,
|
|
): Promise<AiHardwareDevice> {
|
|
if (!/^[0-9]{6}$/.test(activationCode)) throw new TypeError('activation_code is invalid');
|
|
assertAgentId(agentId);
|
|
const operationIdValue = operationId(options);
|
|
return mutationRequest('/device-bindings', 'POST', {
|
|
activation_code: activationCode,
|
|
agent_id: agentId,
|
|
}, operationIdValue, readDevice) as Promise<AiHardwareDevice>;
|
|
}
|
|
|
|
export async function getAiHardwareAgentConfiguration(
|
|
agentId: string,
|
|
): Promise<VersionedAiHardwareResult<AiHardwareAgentConfiguration>> {
|
|
assertAgentId(agentId);
|
|
return request(`/agents/${encodeURIComponent(agentId)}`, undefined, readConfiguration, true) as Promise<VersionedAiHardwareResult<AiHardwareAgentConfiguration>>;
|
|
}
|
|
|
|
export async function updateAiHardwareAgentConfiguration(
|
|
agentId: string,
|
|
revision: number,
|
|
update: AiHardwareAgentConfigurationUpdate,
|
|
options?: AiHardwareMutationOptions,
|
|
): Promise<VersionedAiHardwareResult<AiHardwareAgentConfiguration>> {
|
|
assertAgentId(agentId);
|
|
assertRevision(revision);
|
|
validateUpdate(update);
|
|
const operationIdValue = operationId(options);
|
|
return mutationRequest(`/agents/${encodeURIComponent(agentId)}`, 'PATCH', {
|
|
revision,
|
|
...update,
|
|
}, operationIdValue, readConfiguration, true) as Promise<VersionedAiHardwareResult<AiHardwareAgentConfiguration>>;
|
|
}
|
|
|
|
export async function getAiHardwareAssignment(
|
|
deviceId: string,
|
|
): Promise<VersionedAiHardwareResult<AiHardwareDevice>> {
|
|
assertString(deviceId, 'device_id', 36);
|
|
return request(`/devices/${encodeURIComponent(deviceId)}/agent-assignment`, undefined, readDevice, true) as Promise<VersionedAiHardwareResult<AiHardwareDevice>>;
|
|
}
|
|
|
|
export async function updateAiHardwareAssignment(
|
|
deviceId: string,
|
|
revision: number,
|
|
agentId: string,
|
|
options?: AiHardwareMutationOptions,
|
|
): Promise<VersionedAiHardwareResult<AiHardwareDevice>> {
|
|
assertString(deviceId, 'device_id', 36);
|
|
assertRevision(revision);
|
|
assertAgentId(agentId);
|
|
const operationIdValue = operationId(options);
|
|
return mutationRequest(`/devices/${encodeURIComponent(deviceId)}/agent-assignment`, 'PUT', {
|
|
revision,
|
|
agent_id: agentId,
|
|
}, operationIdValue, readDevice, true) as Promise<VersionedAiHardwareResult<AiHardwareDevice>>;
|
|
}
|