Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
- 模型查询详情应提供可直接用于生成命令的参数值;数字枚举按对应 IDL 显式转换,不按数组位置或展示文案推断。
- 覆盖 IDL 已定义且生成入口可消费的枚举;未知枚举直接跳过,不猜测或回退到其他值。已定义但没有 CLI 请求表达方式的枚举须明确识别为不可用。
- 内部配置指纹不向用户输出;原始配置缓存和用户展示结构分离,保留可选字段缺失与零值的区别。
- 模型级 `is_default` 不向 CLI 用户输出;服务端默认标记不等于用户授权代选模型。参数级默认值继续展示,原始配置缓存保持完整。
- 模型发现验收必须分别核对用户确认的预期模型集合与真实接口返回值转换;CLI 与接口同时少返回模型不能判为完整通过。请求携带 PPE 头也不能替代实际路由和生效配置证据。
- `Seedance_2.0_mini` 和 `Seedance_2.0_mini_lite` 允许省略生成分辨率,由服务端默认 `720p`;CLI 不主动补值,不因模型查询缺少分辨率维度而将该参数标为必填或伪造配置。

- 安装引导的最小兼容修复只补必需命令检查;可选参数已有 `--help` 判断约定时复用该约定,不另增能力返回字段或升级条件。

# 短剧 Skill 描述约定

- description 应覆盖剧本创作与解析、资产素材生成、故事板规划生成、视频产物交付的完整能力及触发场景,不能将短剧 Agent 缩窄为剧本文本创作或文件查询工具。
1 change: 1 addition & 0 deletions internal/models/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
out[key] = value
}
delete(out, "config_key")
delete(out, "is_default") // A server default does not authorize model selection.
warnings := []string{}
warn := func(message string) { warnings = append(warnings, message) }
ratios := make([]string, 0, len(source.Ratios))
Expand Down
4 changes: 2 additions & 2 deletions internal/models/describe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func description(t *testing.T, raw string) map[string]json.RawMessage {
}

func TestDescriptionCLIParametersAndRawPreservation(t *testing.T) {
raw := `{"key":"MiniMax-H3","supported_ratio_list":[0,2,13,3,4,5,6],"default_ratio":3,"config_key":"internal",
raw := `{"key":"MiniMax-H3","is_default":true,"supported_ratio_list":[0,2,13,3,4,5,6],"default_ratio":3,"config_key":"internal",
"future_field":9007199254740993,"audio_total_limit":0,"max_image_size":31457280,"min_video_duration":2000,
"supported_duration_list":[{"value":999}],"default_duration_value":999,
"parameter_config":{"dimensions":[
Expand All @@ -30,7 +30,7 @@ func TestDescriptionCLIParametersAndRawPreservation(t *testing.T) {
{"key":"seed","default_value":"random"}],"need_available_combinations":true}}
`
out := description(t, raw)
for _, key := range []string{"config_key", "supported_ratio_list", "default_ratio", "supported_duration_list", "default_duration_value", "audio_total_limit", "max_image_size"} {
for _, key := range []string{"config_key", "is_default", "supported_ratio_list", "default_ratio", "supported_duration_list", "default_duration_value", "audio_total_limit", "max_image_size"} {
if _, ok := out[key]; ok {
t.Fatalf("unconverted field %s", key)
}
Expand Down
7 changes: 3 additions & 4 deletions internal/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ type Catalog struct {
}

type Summary struct {
Key string `json:"key"`
Name string `json:"name"`
Kind string `json:"kind"`
IsDefault bool `json:"is_default"`
Key string `json:"key"`
Name string `json:"name"`
Kind string `json:"kind"`
}

type Result struct {
Expand Down
14 changes: 14 additions & 0 deletions internal/models/models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,20 @@ func TestModelConfigurationPreservedAcrossCache(t *testing.T) {
if err != nil || !strings.Contains(string(raw), "9007199254740993") || !strings.Contains(string(raw), `"audio_total_limit":0`) {
t.Fatalf("configuration lost values: %s %v", raw, err)
}
var details map[string]json.RawMessage
if err := json.Unmarshal(raw, &details); err != nil {
t.Fatal(err)
}
if _, exists := details["is_default"]; exists {
t.Fatal("model detail must not expose the server default marker")
}
list, err := json.Marshal(result.Catalog.Search(""))
if err != nil || strings.Contains(string(list), `"is_default"`) {
t.Fatalf("model list must not expose the server default marker: %s %v", list, err)
}
if !strings.Contains(string(result.Catalog.Config.Models[0]), `"is_default":true`) {
t.Fatal("presentation must preserve the raw catalog across cache reads")
}
if len(result.Catalog.Search("新模")) != 1 || len(result.Catalog.Search("missing")) != 0 {
t.Fatal("unexpected search result")
}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pippit-dev/cli",
"version": "1.0.28",
"version": "1.0.29",
"description": "Pippit CLI",
"bin": {
"pippit-tool-cli": "scripts/run.js"
Expand Down
46 changes: 30 additions & 16 deletions scripts/skills.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,28 @@ for (const content of [generalSkill, shortDramaSkill]) {

// The Skill is a self-contained document graph: follow only the selected module
// at runtime, but verify all shipped references and examples offline here.
const skillRoot = path.dirname(generalSkillPath);
const visited = new Set();
function visitDocument(filePath) {
filePath = path.resolve(filePath);
assert(filePath.startsWith(skillRoot + path.sep), `Skill reference escapes its package: ${filePath}`);
if (visited.has(filePath)) return;
visited.add(filePath);
const content = readRequiredFile(filePath);
for (const match of content.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
const target = match[1].split("#")[0];
if (!target || /^[a-z]+:/i.test(target)) continue;
const resolved = path.resolve(path.dirname(filePath), target);
assert(fs.existsSync(resolved), `Broken Skill link in ${filePath}: ${target}`);
if (resolved.endsWith(".md")) visitDocument(resolved);
function collectSkillDocuments(entryPath) {
const skillRoot = path.dirname(entryPath);
const visited = new Set();
function visitDocument(filePath) {
filePath = path.resolve(filePath);
assert(filePath.startsWith(skillRoot + path.sep), `Skill reference escapes its package: ${filePath}`);
if (visited.has(filePath)) return;
visited.add(filePath);
const content = readRequiredFile(filePath);
for (const match of content.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
const target = match[1].split("#")[0];
if (!target || /^[a-z]+:/i.test(target)) continue;
const resolved = path.resolve(path.dirname(filePath), target);
assert(fs.existsSync(resolved), `Broken Skill link in ${filePath}: ${target}`);
if (resolved.endsWith(".md")) visitDocument(resolved);
}
}
visitDocument(entryPath);
return visited;
}
visitDocument(generalSkillPath);
const skillRoot = path.dirname(generalSkillPath);
const visited = collectSkillDocuments(generalSkillPath);
const skillDocuments = [...visited].map((file) => readRequiredFile(file)).join("\n");
const commandModules = {
auth: ["status", "login", "logout"],
Expand Down Expand Up @@ -103,9 +108,18 @@ function checkSkillFiles(dir) {
}
checkSkillFiles(skillRoot);

const shortDramaVisited = collectSkillDocuments(shortDramaSkillPath);
const shortDramaDocuments = [...shortDramaVisited].map(readRequiredFile).join("\n");
for (const folder of ["commands", "workflows", "examples", "scripts"]) {
for (const file of fs.readdirSync(path.join(path.dirname(shortDramaSkillPath), folder))) {
if (file.endsWith(".md")) {
assert(shortDramaVisited.has(path.join(path.dirname(shortDramaSkillPath), folder, file)), `Unreachable short-drama document: ${folder}/${file}`);
}
}
}
for (const requiredText of ["request_user_input", "ask_user_question", "credits"]) {
assert.ok(
shortDramaSkill.includes(requiredText),
shortDramaDocuments.includes(requiredText),
`xyq-short-drama-skill missing contract: ${requiredText}`,
);
}
Expand Down
Loading
Loading