feat(frontend): align skills bootstrap flow to titan contract

This commit is contained in:
肖应宇 2026-04-07 13:29:13 +08:00
parent 8b3914a999
commit 75f62e7c15
5 changed files with 38 additions and 91 deletions

View File

@ -1,34 +0,0 @@
import assert from "node:assert/strict";
import test from "node:test";
const { normalizeBootstrapRemoteSkillRequest } = await import(
new URL("./normalize-bootstrap.ts", import.meta.url).href
);
void test("keeps content_ids as primary contract", () => {
const normalized = normalizeBootstrapRemoteSkillRequest({
thread_id: "t1",
content_ids: [11, 22],
});
assert.deepEqual(normalized.content_ids, [11, 22]);
});
void test("maps legacy content_id to content_ids for compatibility", () => {
const normalized = normalizeBootstrapRemoteSkillRequest({
thread_id: "t1",
content_id: 7,
});
assert.deepEqual(normalized.content_ids, [7]);
});
void test("throws when neither content_ids nor content_id is provided", () => {
assert.throws(
() =>
normalizeBootstrapRemoteSkillRequest({
thread_id: "t1",
}),
/content_ids is required/,
);
});

View File

@ -1,9 +1,6 @@
import { getBackendBaseURL } from "@/core/config";
import {
normalizeBootstrapRemoteSkillRequest,
} from "./normalize-bootstrap";
import type { Skill } from "./types";
import type { Skill } from "./type";
export async function loadSkills() {
const skills = await fetch(`${getBackendBaseURL()}/api/skills`);
@ -38,6 +35,7 @@ export interface InstallSkillResponse {
message: string;
}
// [移植自 main 分支 ef9a071] 添加 skill yaml 解析和远程 skill 初始化 API
export interface MaterializeSkillYamlRequest {
thread_id: string;
path: string;
@ -55,9 +53,7 @@ export interface MaterializeSkillYamlResponse {
export interface BootstrapRemoteSkillRequest {
thread_id: string;
content_ids?: number[];
// Legacy input, kept for minimal compatibility at the API boundary.
content_id?: number;
content_id: number;
language_type?: number;
target_dir?: string;
clear_target?: boolean;
@ -98,9 +94,14 @@ export async function installSkill(
return response.json();
}
// [移植自 main 分支 ef9a071] 解析 skill.yaml 文件并创建目录结构
export async function materializeSkillYaml(
request: MaterializeSkillYamlRequest,
): Promise<MaterializeSkillYamlResponse> {
console.log("[skills/api] ========== materializeSkillYaml START ==========");
console.log("[skills/api] request:", JSON.stringify(request, null, 2));
console.log("[skills/api] API URL:", `${getBackendBaseURL()}/api/skills/materialize-yaml`);
const response = await fetch(
`${getBackendBaseURL()}/api/skills/materialize-yaml`,
{
@ -112,20 +113,35 @@ export async function materializeSkillYaml(
},
);
console.log("[skills/api] response status:", response.status, response.statusText);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMessage =
errorData.detail ?? `HTTP ${response.status}: ${response.statusText}`;
console.error("[skills/api] materializeSkillYaml FAILED:", errorMessage);
console.error("[skills/api] error data:", errorData);
throw new Error(errorMessage);
}
return response.json();
const result = await response.json();
console.log("[skills/api] materializeSkillYaml SUCCESS:", result);
console.log("[skills/api] ========== materializeSkillYaml END ==========");
return result;
}
// [移植自 main 分支 ef9a071] 从远程平台获取 skill 并初始化
export async function bootstrapRemoteSkill(
request: BootstrapRemoteSkillRequest,
): Promise<BootstrapRemoteSkillResponse> {
const normalizedRequest = normalizeBootstrapRemoteSkillRequest(request);
console.log("[skills/api] ========== bootstrapRemoteSkill START ==========");
console.log("[skills/api] request:", JSON.stringify(request, null, 2));
console.log("[skills/api] thread_id:", request.thread_id);
console.log("[skills/api] content_id:", request.content_id);
console.log("[skills/api] language_type:", request.language_type);
console.log("[skills/api] target_dir:", request.target_dir);
console.log("[skills/api] API URL:", `${getBackendBaseURL()}/api/skills/bootstrap-remote`);
const response = await fetch(
`${getBackendBaseURL()}/api/skills/bootstrap-remote`,
{
@ -133,16 +149,26 @@ export async function bootstrapRemoteSkill(
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(normalizedRequest),
body: JSON.stringify(request),
},
);
console.log("[skills/api] response status:", response.status, response.statusText);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMessage =
errorData.detail ?? `HTTP ${response.status}: ${response.statusText}`;
console.error("[skills/api] bootstrapRemoteSkill FAILED:", errorMessage);
console.error("[skills/api] error data:", errorData);
throw new Error(errorMessage);
}
return response.json();
const result = await response.json();
console.log("[skills/api] bootstrapRemoteSkill SUCCESS:", result);
console.log("[skills/api] created_directories:", result.created_directories);
console.log("[skills/api] created_files:", result.created_files);
console.log("[skills/api] sandbox_id:", result.sandbox_id);
console.log("[skills/api] ========== bootstrapRemoteSkill END ==========");
return result;
}

View File

@ -1,44 +0,0 @@
export interface BootstrapRemoteSkillRequestLike {
thread_id: string;
content_ids?: number[];
content_id?: number;
language_type?: number;
target_dir?: string;
clear_target?: boolean;
}
export interface NormalizedBootstrapRemoteSkillRequest
extends Omit<BootstrapRemoteSkillRequestLike, "content_id" | "content_ids"> {
content_ids: number[];
}
export function normalizeBootstrapRemoteSkillRequest(
request: BootstrapRemoteSkillRequestLike,
): NormalizedBootstrapRemoteSkillRequest {
const normalizedContentIds = Array.isArray(request.content_ids)
? request.content_ids
.map((id) => Number(id))
.filter((id) => Number.isFinite(id) && id > 0)
: [];
const legacyContentId =
request.content_id != null && Number.isFinite(Number(request.content_id))
? Number(request.content_id)
: undefined;
const contentIds =
normalizedContentIds.length > 0
? normalizedContentIds
: legacyContentId != null
? [legacyContentId]
: [];
if (contentIds.length === 0) {
throw new Error("content_ids is required.");
}
return {
...request,
content_ids: contentIds,
};
}

View File

@ -1 +0,0 @@
export type { Skill } from "./type";

View File

@ -79,7 +79,7 @@ export function useSelectedSkillListener({
try {
const result = await bootstrapRemoteSkill({
thread_id: threadId,
content_ids: [Number(id)],
content_id: Number(id),
language_type: languageType,
target_dir: "/mnt/user-data/uploads/skill",
clear_target: true,