diff --git a/arkruntime/model/skill/oas_json_gen.go b/arkruntime/model/skill/oas_json_gen.go index 3422a94..b121b26 100644 --- a/arkruntime/model/skill/oas_json_gen.go +++ b/arkruntime/model/skill/oas_json_gen.go @@ -94,6 +94,69 @@ func (s *CreateSkillRequest) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *CreateSkillVersionRequest) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *CreateSkillVersionRequest) encodeFields(e *jx.Encoder) { + { + if s.DisplayTitle.Set { + e.FieldStart("display_title") + s.DisplayTitle.Encode(e) + } + } +} + +var jsonFieldsNameOfCreateSkillVersionRequest = [1]string{ + 0: "display_title", +} + +// Decode decodes CreateSkillVersionRequest from json. +func (s *CreateSkillVersionRequest) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode CreateSkillVersionRequest to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "display_title": + if err := func() error { + s.DisplayTitle.Reset() + if err := s.DisplayTitle.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"display_title\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode CreateSkillVersionRequest") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *CreateSkillVersionRequest) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *CreateSkillVersionRequest) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode encodes bool as json. func (o OptBool) Encode(e *jx.Encoder) { if !o.Set { @@ -449,3 +512,237 @@ func (s *SkillObject) UnmarshalJSON(data []byte) error { d := jx.DecodeBytes(data) return s.Decode(d) } + +// Encode implements json.Marshaler. +func (s *SkillVersion) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *SkillVersion) encodeFields(e *jx.Encoder) { + { + e.FieldStart("id") + e.Str(s.ID) + } + { + e.FieldStart("type") + s.Type.Encode(e) + } + { + e.FieldStart("skill_id") + e.Str(s.SkillID) + } + { + e.FieldStart("version") + e.Str(s.Version) + } + { + e.FieldStart("display_title") + e.Str(s.DisplayTitle) + } + { + if s.Description.Set { + e.FieldStart("description") + s.Description.Encode(e) + } + } + { + e.FieldStart("created_at") + e.Str(s.CreatedAt) + } +} + +var jsonFieldsNameOfSkillVersion = [7]string{ + 0: "id", + 1: "type", + 2: "skill_id", + 3: "version", + 4: "display_title", + 5: "description", + 6: "created_at", +} + +// Decode decodes SkillVersion from json. +func (s *SkillVersion) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SkillVersion to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.ID = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"id\"") + } + case "type": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Type.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"type\"") + } + case "skill_id": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Str() + s.SkillID = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"skill_id\"") + } + case "version": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + v, err := d.Str() + s.Version = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"version\"") + } + case "display_title": + requiredBitSet[0] |= 1 << 4 + if err := func() error { + v, err := d.Str() + s.DisplayTitle = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"display_title\"") + } + case "description": + if err := func() error { + s.Description.Reset() + if err := s.Description.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"description\"") + } + case "created_at": + requiredBitSet[0] |= 1 << 6 + if err := func() error { + v, err := d.Str() + s.CreatedAt = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"created_at\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode SkillVersion") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b01011111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfSkillVersion) { + name = jsonFieldsNameOfSkillVersion[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *SkillVersion) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SkillVersion) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes SkillVersionType as json. +func (s SkillVersionType) Encode(e *jx.Encoder) { + e.Str(string(s)) +} + +// Decode decodes SkillVersionType from json. +func (s *SkillVersionType) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SkillVersionType to nil") + } + v, err := d.StrBytes() + if err != nil { + return err + } + // Try to use constant string. + switch SkillVersionType(v) { + case SkillVersionTypeSkillVersion: + *s = SkillVersionTypeSkillVersion + default: + *s = SkillVersionType(v) + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s SkillVersionType) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SkillVersionType) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} diff --git a/arkruntime/model/skill/oas_parameters_gen.go b/arkruntime/model/skill/oas_parameters_gen.go index ca7e792..18a71db 100644 --- a/arkruntime/model/skill/oas_parameters_gen.go +++ b/arkruntime/model/skill/oas_parameters_gen.go @@ -11,6 +11,11 @@ type SkillVersionContentDownloadParams struct { Version string } +// SkillVersionsCreateParams is parameters of SkillVersions_create operation. +type SkillVersionsCreateParams struct { + SkillId string +} + // SkillsRetrieveParams is parameters of Skills_retrieve operation. type SkillsRetrieveParams struct { SkillId string diff --git a/arkruntime/model/skill/oas_schemas_gen.go b/arkruntime/model/skill/oas_schemas_gen.go index a9717f1..84891c7 100644 --- a/arkruntime/model/skill/oas_schemas_gen.go +++ b/arkruntime/model/skill/oas_schemas_gen.go @@ -37,6 +37,22 @@ func (s *CreateSkillRequest) SetProtectionEnabled(val OptBool) { s.ProtectionEnabled = val } +// Ref: #/components/schemas/CreateSkillVersionRequest +type CreateSkillVersionRequest struct { + // Skill 版本展示名。. + DisplayTitle OptString `json:"display_title" form:"display_title"` +} + +// GetDisplayTitle returns the value of DisplayTitle. +func (s *CreateSkillVersionRequest) GetDisplayTitle() OptString { + return s.DisplayTitle +} + +// SetDisplayTitle sets the value of DisplayTitle. +func (s *CreateSkillVersionRequest) SetDisplayTitle(val OptString) { + s.DisplayTitle = val +} + // NewOptBool returns new OptBool with value set to v. func NewOptBool(v bool) OptBool { return OptBool{ @@ -292,6 +308,97 @@ func (s *SkillObject) UnmarshalText(data []byte) error { } } +// Skill 版本摘要。 +// CreateSkillVersion / ListSkillVersions / GetSkillVersion 返回该对象;主 +// Skill 的 latest_version 会由服务端在新版本创建成功后推进。. +// Ref: #/components/schemas/SkillVersion +type SkillVersion struct { + // SkillVersion ID,形如 `skillver-...`。. + ID string `json:"id"` + // 固定 `"skill_version"`。. + Type SkillVersionType `json:"type"` + // 归属 Skill ID。. + SkillID string `json:"skill_id"` + // 版本号,服务端按字符串形式自增,例如 `"1"`、`"2"`。. + Version string `json:"version"` + // 该版本的展示名。. + DisplayTitle string `json:"display_title"` + // 该版本的描述信息。. + Description OptString `json:"description"` + // 创建时间,RFC 3339 UTC 秒级精度。. + CreatedAt string `json:"created_at"` +} + +// GetID returns the value of ID. +func (s *SkillVersion) GetID() string { + return s.ID +} + +// GetType returns the value of Type. +func (s *SkillVersion) GetType() SkillVersionType { + return s.Type +} + +// GetSkillID returns the value of SkillID. +func (s *SkillVersion) GetSkillID() string { + return s.SkillID +} + +// GetVersion returns the value of Version. +func (s *SkillVersion) GetVersion() string { + return s.Version +} + +// GetDisplayTitle returns the value of DisplayTitle. +func (s *SkillVersion) GetDisplayTitle() string { + return s.DisplayTitle +} + +// GetDescription returns the value of Description. +func (s *SkillVersion) GetDescription() OptString { + return s.Description +} + +// GetCreatedAt returns the value of CreatedAt. +func (s *SkillVersion) GetCreatedAt() string { + return s.CreatedAt +} + +// SetID sets the value of ID. +func (s *SkillVersion) SetID(val string) { + s.ID = val +} + +// SetType sets the value of Type. +func (s *SkillVersion) SetType(val SkillVersionType) { + s.Type = val +} + +// SetSkillID sets the value of SkillID. +func (s *SkillVersion) SetSkillID(val string) { + s.SkillID = val +} + +// SetVersion sets the value of Version. +func (s *SkillVersion) SetVersion(val string) { + s.Version = val +} + +// SetDisplayTitle sets the value of DisplayTitle. +func (s *SkillVersion) SetDisplayTitle(val string) { + s.DisplayTitle = val +} + +// SetDescription sets the value of Description. +func (s *SkillVersion) SetDescription(val OptString) { + s.Description = val +} + +// SetCreatedAt sets the value of CreatedAt. +func (s *SkillVersion) SetCreatedAt(val string) { + s.CreatedAt = val +} + // SkillVersionContentDownloadFound is response for SkillVersionContentDownload operation. type SkillVersionContentDownloadFound struct { Location string @@ -306,3 +413,38 @@ func (s *SkillVersionContentDownloadFound) GetLocation() string { func (s *SkillVersionContentDownloadFound) SetLocation(val string) { s.Location = val } + +// 固定 `"skill_version"`。. +type SkillVersionType string + +const ( + SkillVersionTypeSkillVersion SkillVersionType = "skill_version" +) + +// AllValues returns all SkillVersionType values. +func (SkillVersionType) AllValues() []SkillVersionType { + return []SkillVersionType{ + SkillVersionTypeSkillVersion, + } +} + +// MarshalText implements encoding.TextMarshaler. +func (s SkillVersionType) MarshalText() ([]byte, error) { + switch s { + case SkillVersionTypeSkillVersion: + return []byte(s), nil + default: + return nil, errors.Errorf("invalid value: %q", s) + } +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (s *SkillVersionType) UnmarshalText(data []byte) error { + switch SkillVersionType(data) { + case SkillVersionTypeSkillVersion: + *s = SkillVersionTypeSkillVersion + return nil + default: + return errors.Errorf("invalid value: %q", data) + } +} diff --git a/arkruntime/model/skill/oas_validators_gen.go b/arkruntime/model/skill/oas_validators_gen.go index b6daed0..7337300 100644 --- a/arkruntime/model/skill/oas_validators_gen.go +++ b/arkruntime/model/skill/oas_validators_gen.go @@ -41,3 +41,35 @@ func (s SkillObject) Validate() error { return errors.Errorf("invalid value: %v", s) } } + +func (s *SkillVersion) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Type.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "type", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s SkillVersionType) Validate() error { + switch s { + case "skill_version": + return nil + default: + return errors.Errorf("invalid value: %v", s) + } +} diff --git a/arkruntime/model/skill/skill_shim.go b/arkruntime/model/skill/skill_shim.go index 5c063ab..e99e9c1 100644 --- a/arkruntime/model/skill/skill_shim.go +++ b/arkruntime/model/skill/skill_shim.go @@ -18,6 +18,12 @@ type SkillResponse struct { model.HttpHeader } +// SkillVersionResponse wraps SkillVersion so it satisfies model.Response. +type SkillVersionResponse struct { + SkillVersion + model.HttpHeader +} + // UploadForm pairs the multipart metadata (display_title) with the binary // zip file part. The typespec-generated CreateSkillRequest describes the // wire fields; the binary `files` part is appended here at multipart build diff --git a/arkruntime/self_hosted_client_test.go b/arkruntime/self_hosted_client_test.go index d3def25..0a1208a 100644 --- a/arkruntime/self_hosted_client_test.go +++ b/arkruntime/self_hosted_client_test.go @@ -294,6 +294,59 @@ func TestCreateSkillWithOptionsMultipartContract(t *testing.T) { } } +func TestCreateSkillVersionMultipartContract(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost || r.URL.Path != "/skills/skill-1/versions" { + http.NotFound(w, r) + return + } + if err := r.ParseMultipartForm(1024); err != nil { + t.Fatalf("ParseMultipartForm() error = %v", err) + } + if got := r.FormValue("display_title"); got != "Readiness Skill v2" { + t.Fatalf("display_title = %q", got) + } + if got := r.FormValue("protection_enabled"); got != "" { + t.Fatalf("protection_enabled = %q", got) + } + if files := r.MultipartForm.File["files"]; len(files) != 1 || files[0].Filename != "skill.zip" { + t.Fatalf("files = %+v", files) + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"id":"skillver-2","type":"skill_version","skill_id":"skill-1","version":"2","display_title":"Readiness Skill v2","description":"ok","created_at":"2026-09-14T10:11:12Z"}`)) + })) + defer server.Close() + + client := NewClientWithApiKey("test-api-key", WithBaseUrl(server.URL)) + out, err := client.CreateSkillVersion( + context.Background(), + "skill-1", + strings.NewReader("zip-bytes-v2"), + "skill.zip", + "Readiness Skill v2", + ) + if err != nil { + t.Fatalf("CreateSkillVersion() error = %v", err) + } + if out == nil || out.ID != "skillver-2" || out.SkillID != "skill-1" || out.Version != "2" { + t.Fatalf("CreateSkillVersion() = %+v", out) + } +} + +func TestCreateSkillVersionValidation(t *testing.T) { + client := NewClientWithApiKey("test-api-key", WithBaseUrl("https://example.com")) + + _, err := client.CreateSkillVersion(context.Background(), "", strings.NewReader("zip-bytes"), "skill.zip", "title") + if err == nil || err.Error() != "missing required skill_id" { + t.Fatalf("CreateSkillVersion() error = %v, want missing required skill_id", err) + } + + _, err = client.CreateSkillVersion(context.Background(), "skill-1", nil, "skill.zip", "title") + if err == nil || err.Error() != "missing required file reader" { + t.Fatalf("CreateSkillVersion() error = %v, want missing required file reader", err) + } +} + func TestSendSessionEventRaw(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost || r.URL.Path != "/sessions/sess-1/events" { diff --git a/arkruntime/skills.go b/arkruntime/skills.go index a2e02dc..5b779fb 100644 --- a/arkruntime/skills.go +++ b/arkruntime/skills.go @@ -22,6 +22,11 @@ type CreateSkillOptions struct { ProtectionEnabled *bool } +// CreateSkillVersionOptions controls optional multipart metadata for CreateSkillVersion. +type CreateSkillVersionOptions struct { + DisplayTitle string +} + // CreateSkill uploads a zip package as multipart/form-data and creates a Skill. // `fileReader` supplies the zip bytes; `displayTitle` is optional. func (c *Client) CreateSkill( @@ -67,6 +72,56 @@ func (c *Client) CreateSkillWithOptions( return &wrap.Skill, nil } +// CreateSkillVersion uploads a zip package as multipart/form-data and creates a Skill version. +// `fileReader` supplies the zip bytes; `displayTitle` is optional. +func (c *Client) CreateSkillVersion( + ctx context.Context, + skillID string, + fileReader io.Reader, + fileName, displayTitle string, + setters ...requestOption, +) (*skill.SkillVersion, error) { + return c.CreateSkillVersionWithOptions(ctx, skillID, fileReader, fileName, CreateSkillVersionOptions{ + DisplayTitle: displayTitle, + }, setters...) +} + +// CreateSkillVersionWithOptions uploads a zip package as a new version of an existing Skill. +func (c *Client) CreateSkillVersionWithOptions( + ctx context.Context, + skillID string, + fileReader io.Reader, + fileName string, + options CreateSkillVersionOptions, + setters ...requestOption, +) (*skill.SkillVersion, error) { + if skillID == "" { + return nil, errors.New("missing required skill_id") + } + if fileReader == nil { + return nil, errors.New("missing required file reader") + } + form := &skill.UploadForm{ + File: fileReader, + FileName: fileName, + DisplayTitle: options.DisplayTitle, + } + body, contentType, merr := form.MarshalMultipart() + if merr != nil { + return nil, merr + } + opts := append(setters, + withBody(bytes.NewReader(body)), + withContentType(contentType), + ) + u := c.fullURL(fmt.Sprintf("%s/%s/versions", skillsPrefix, skill.PathEscape(skillID))) + wrap := &skill.SkillVersionResponse{} + if err := c.Do(ctx, http.MethodPost, u, "", "", wrap, opts...); err != nil { + return nil, err + } + return &wrap.SkillVersion, nil +} + // GetSkill retrieves a Skill summary by ID. func (c *Client) GetSkill( ctx context.Context,