File size: 3,185 Bytes
f0634fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | /**
* Submit user feedback to the managed Kimi Code platform.
*
* POSTs a JSON body to `{kimiCodeBaseUrl}/feedback` with a Bearer access
* token. The client tags `version` with a `kimi-code-` prefix so the
* backend can identify this client.
*/
import { readApiErrorMessage } from './api-error';
import { kimiCodeBaseUrl } from './managed-usage';
export interface SubmitFeedbackBody {
readonly session_id: string;
readonly content: string;
readonly version: string;
readonly os: string;
readonly model: string | null;
readonly contact?: string;
readonly info?: Record<string, unknown>;
}
export interface FetchSubmitFeedbackOk {
readonly kind: 'ok';
readonly feedbackId: number;
}
export interface FetchSubmitFeedbackError {
readonly kind: 'error';
readonly status?: number;
readonly message: string;
}
export type FetchSubmitFeedbackResult = FetchSubmitFeedbackOk | FetchSubmitFeedbackError;
export function kimiCodeFeedbackUrl(baseUrl?: string): string {
return `${(baseUrl ?? kimiCodeBaseUrl()).replace(/\/+$/, '')}/feedback`;
}
export async function fetchSubmitFeedback(
url: string,
accessToken: string,
body: SubmitFeedbackBody,
opts: { timeoutMs?: number } = {},
): Promise<FetchSubmitFeedbackResult> {
const controller = new AbortController();
const timer = setTimeout(() => {
controller.abort();
}, opts.timeoutMs ?? 8000);
try {
const res = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
signal: controller.signal,
});
if (!res.ok) {
return {
kind: 'error',
status: res.status,
message: await readApiErrorMessage(
res,
`Failed to submit feedback: HTTP ${String(res.status)}`,
),
};
}
const feedbackId = parseFeedbackId(await res.json());
if (feedbackId === undefined) {
return { kind: 'error', message: 'Failed to submit feedback: missing feedback_id.' };
}
return { kind: 'ok', feedbackId };
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
return { kind: 'error', message: 'Failed to submit feedback: request timed out.' };
}
const msg = error instanceof Error ? error.message : String(error);
return { kind: 'error', message: `Failed to submit feedback: ${msg}` };
} finally {
clearTimeout(timer);
}
}
function parseFeedbackId(payload: unknown): number | undefined {
const direct = readFeedbackId(payload);
if (direct !== undefined) return direct;
if (typeof payload === 'object' && payload !== null && 'data' in payload) {
return readFeedbackId((payload as { readonly data: unknown }).data);
}
return undefined;
}
function readFeedbackId(payload: unknown): number | undefined {
if (typeof payload !== 'object' || payload === null) return undefined;
const record = payload as Record<string, unknown>;
const value = record['feedback_id'] ?? record['id'];
return typeof value === 'number' && Number.isInteger(value) ? value : undefined;
}
|