feat(frontend): align skills bootstrap flow to titan contract
This commit is contained in:
parent
c8536c7da3
commit
021f6e3b3c
|
|
@ -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/,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { getBackendBaseURL } from "@/core/config";
|
import { getBackendBaseURL } from "@/core/config";
|
||||||
|
|
||||||
import {
|
import type { Skill } from "./type";
|
||||||
normalizeBootstrapRemoteSkillRequest,
|
|
||||||
} from "./normalize-bootstrap";
|
|
||||||
import type { Skill } from "./types";
|
|
||||||
|
|
||||||
export async function loadSkills() {
|
export async function loadSkills() {
|
||||||
const skills = await fetch(`${getBackendBaseURL()}/api/skills`);
|
const skills = await fetch(`${getBackendBaseURL()}/api/skills`);
|
||||||
|
|
@ -38,6 +35,7 @@ export interface InstallSkillResponse {
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [移植自 main 分支 ef9a071] 添加 skill yaml 解析和远程 skill 初始化 API
|
||||||
export interface MaterializeSkillYamlRequest {
|
export interface MaterializeSkillYamlRequest {
|
||||||
thread_id: string;
|
thread_id: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
|
@ -55,9 +53,7 @@ export interface MaterializeSkillYamlResponse {
|
||||||
|
|
||||||
export interface BootstrapRemoteSkillRequest {
|
export interface BootstrapRemoteSkillRequest {
|
||||||
thread_id: string;
|
thread_id: string;
|
||||||
content_ids?: number[];
|
content_id: number;
|
||||||
// Legacy input, kept for minimal compatibility at the API boundary.
|
|
||||||
content_id?: number;
|
|
||||||
language_type?: number;
|
language_type?: number;
|
||||||
target_dir?: string;
|
target_dir?: string;
|
||||||
clear_target?: boolean;
|
clear_target?: boolean;
|
||||||
|
|
@ -98,9 +94,14 @@ export async function installSkill(
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [移植自 main 分支 ef9a071] 解析 skill.yaml 文件并创建目录结构
|
||||||
export async function materializeSkillYaml(
|
export async function materializeSkillYaml(
|
||||||
request: MaterializeSkillYamlRequest,
|
request: MaterializeSkillYamlRequest,
|
||||||
): Promise<MaterializeSkillYamlResponse> {
|
): 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(
|
const response = await fetch(
|
||||||
`${getBackendBaseURL()}/api/skills/materialize-yaml`,
|
`${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) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
errorData.detail ?? `HTTP ${response.status}: ${response.statusText}`;
|
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);
|
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(
|
export async function bootstrapRemoteSkill(
|
||||||
request: BootstrapRemoteSkillRequest,
|
request: BootstrapRemoteSkillRequest,
|
||||||
): Promise<BootstrapRemoteSkillResponse> {
|
): 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(
|
const response = await fetch(
|
||||||
`${getBackendBaseURL()}/api/skills/bootstrap-remote`,
|
`${getBackendBaseURL()}/api/skills/bootstrap-remote`,
|
||||||
{
|
{
|
||||||
|
|
@ -133,16 +149,26 @@ export async function bootstrapRemoteSkill(
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"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) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
errorData.detail ?? `HTTP ${response.status}: ${response.statusText}`;
|
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);
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export type { Skill } from "./type";
|
|
||||||
|
|
@ -79,7 +79,7 @@ export function useSelectedSkillListener({
|
||||||
try {
|
try {
|
||||||
const result = await bootstrapRemoteSkill({
|
const result = await bootstrapRemoteSkill({
|
||||||
thread_id: threadId,
|
thread_id: threadId,
|
||||||
content_ids: [Number(id)],
|
content_id: Number(id),
|
||||||
language_type: languageType,
|
language_type: languageType,
|
||||||
target_dir: "/mnt/user-data/uploads/skill",
|
target_dir: "/mnt/user-data/uploads/skill",
|
||||||
clear_target: true,
|
clear_target: true,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue